五 関数型プログラミング#
関数は Python に内蔵されているサポートの一つであり、私たちは大きなコードを関数に分割することで、層を重ねた関数呼び出しを通じて複雑なタスクを単純なタスクに分解できます。この分解は手続き型プログラミングと呼ばれます。関数は手続き型プログラミングの基本単位です。
関数型プログラミング(「型」という文字が追加されていることに注意)——Functional Programmingは、手続き型プログラミングに分類されることもありますが、その思想は数学的計算により近いです。
まず、コンピュータ(Computer)と計算(Compute)の概念を理解する必要があります。
コンピュータのレベルでは、CPU は加減乗除の命令コードやさまざまな条件判断およびジャンプ命令を実行します。したがって、アセンブリ言語はコンピュータに最も近い言語です。
一方、計算は数学的な意味での計算を指し、抽象的な計算ほどコンピュータハードウェアから遠くなります。
プログラミング言語に対応させると、低レベルの言語ほどコンピュータに近く、抽象度が低く、実行効率が高い(例えば C 言語)、高レベルの言語ほど計算に近く、抽象度が高く、実行効率が低い(例えば Lisp 言語)です。
関数型プログラミングは、純粋な関数型プログラミング言語で書かれた関数には変数がないという非常に高い抽象度のプログラミングパラダイムです。したがって、任意の関数は、入力が確定していれば、出力も確定します。このような純粋な関数は副作用がないと呼ばれます。一方、変数を使用することを許可するプログラミング言語では、関数内部の変数の状態が不確定であり、同じ入力に対して異なる出力を得る可能性があるため、これらの関数は副作用があります。
関数型プログラミングの特徴の一つは、関数自体を別の関数に引数として渡すことができ、関数を返すことも許可されていることです!
Python は関数型プログラミングを部分的にサポートしています。Python は変数の使用を許可しているため、Python は純粋な関数型プログラミング言語ではありません。
高階関数(Higher-order function)
変数は関数を指すことができる
Python に内蔵されている絶対値を求める関数 abs () を例にとると、この関数を呼び出すには以下のコードを使用します:
>>> abs(-10)
10
しかし、abs だけを書いた場合はどうなるでしょうか?
>>> abs
<built-in function abs>
ここで、abs (-10) は関数呼び出しであり、abs は関数そのものです。
関数呼び出しの結果を変数に代入することができます:
>>> x = abs(-10)
>>> x
10
しかし、関数そのものを変数に代入した場合はどうなるでしょうか?
>>> f = abs
>>> f
<built-in function abs>
結論:関数そのものも変数に代入できる、つまり、変数は関数を指すことができる。
もし変数が関数を指している場合、その変数を使ってこの関数を呼び出すことはできるでしょうか?コードで確認してみましょう:
>>> f = abs
>>> f(-10)
10
成功しました!変数 f は現在 abs 関数そのものを指しています。abs () 関数を直接呼び出すことと、変数 f () を呼び出すことは全く同じです。
関数名も変数である
では、関数名とは何でしょうか?関数名は実際には関数を指す変数です!abs () という関数に対して、関数名 abs を変数として見ることができ、絶対値を計算できる関数を指しています!
もし abs を他のオブジェクトに指し示すと、どのような状況が発生するでしょうか?
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
abs を 10 に指し示した後、abs (-10) でその関数を呼び出すことができなくなりました!なぜなら、abs という変数は絶対値を求める関数を指すのではなく、整数 10 を指しているからです!
もちろん、実際のコードでは絶対にこのように書いてはいけません。ここでは関数名も変数であることを説明するためです。abs 関数を復元するには、Python インタラクティブ環境を再起動してください。
注:abs 関数は実際にはimport builtinsモジュールに定義されているため、他のモジュールでも abs 変数の指し示す先を変更するには、import builtins; builtins.abs = 10 を使用します。
関数を渡す
変数が関数を指すことができ、関数の引数が変数を受け取ることができるので、ある関数は別の関数を引数として受け取ることができ、このような関数を高階関数と呼びます。
最も単純な高階関数:
def add(x, y, f):
return f(x) + f(y)
add (-5, 6, abs) を呼び出すと、引数 x、y、f はそれぞれ - 5、6、abs を受け取ります。関数定義に基づいて、計算過程を推測できます:
x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
return 11
高階関数を作成することは、関数の引数が他の関数を受け取ることができるようにすることです。
小結
関数を引数として渡すこのような関数を高階関数と呼び、関数型プログラミングはこのような高度に抽象化されたプログラミングパラダイムを指します。
1.1 map/reduce
Python には map () と reduce () 関数が内蔵されています。
map () 関数は 2 つの引数を受け取ります。一つは関数、もう一つはIterableで、map は渡された関数を順次シーケンスの各要素に適用し、結果を新しい Iteratorとして返します。
例を示します。例えば、関数 f (x)=x^2 を持っていて、この関数をリスト [1, 2, 3, 4, 5, 6, 7, 8, 9] に適用したい場合、map () を使って次のように実現できます。
今、Python コードで実装してみましょう:
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
map () に渡された最初の引数は f、つまり関数オブジェクトそのものです。結果 r は Iterator であり、Iterator は遅延シーケンスであるため、list () 関数を使って全シーケンスを計算し、リストを返します。
あなたは考えるかもしれません。map () 関数は必要なく、ループを書けば結果を計算できます:
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
L.append(f(n))
print(L)
確かに可能ですが、上記のループコードから「f (x) をリストの各要素に適用し、結果を新しいリストとして生成する」ということを一目で理解できますか?
したがって、map () は高階関数として、実際には計算ルールを抽象化しているため、単純な f (x)=x^2 を計算するだけでなく、任意に複雑な関数を計算することもできます。例えば、このリストのすべての数字を文字列に変換することができます:
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
たった一行のコードで。
次に reduce の使い方を見てみましょう。reduce は関数をシーケンス [x1, x2, x3, ...] に適用し、この関数は 2 つの引数を受け取り、reduce は結果を次の要素と累積計算します。その効果は次のようになります:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
例えば、シーケンスの合計を求めるには、reduce を使って次のように実現できます:
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
もちろん、合計計算は Python の内蔵関数 sum () を使って直接行うことができるので、reduce を使う必要はありません。
しかし、シーケンス [1, 3, 5, 7, 9] を整数 13579 に変換したい場合、reduce が役立ちます:
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
この例自体にはあまり価値はありませんが、文字列 str もシーケンスであることを考慮すると、上記の例を少し変更し、map () と組み合わせることで、str を int に変換する関数を書くことができます:
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> def char2num(s):
... digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
... return digits[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579
整理して str2int 関数を作成すると次のようになります:
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))
さらに lambda 関数を使ってコードを簡略化することもできます:
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
つまり、Python が int () 関数を提供していなかったと仮定すると、文字列を整数に変換する関数を自分で書くことができ、しかも数行のコードで済むのです!
1.2 filter
Python に内蔵されている filter () 関数はシーケンスをフィルタリングするために使用されます。
map () と似て、filter () も関数とシーケンスを受け取ります。map () とは異なり、filter () は渡された関数を各要素に適用し、戻り値が True か False かに基づいてその要素を保持するか破棄するかを決定します。
例えば、リストの中から偶数を削除し、奇数だけを保持するには、次のように書くことができます:
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 結果: [1, 5, 9, 15]
シーケンスの中から空の文字列を削除するには、次のように書くことができます:
def not_empty(s):
return s and s.strip()
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 結果: ['A', 'B', 'C']
filter () という高階関数を使用する際の重要な点は、正しく「フィルタリング」関数を実装することです。
filter () 関数が返すのはIterator、つまり遅延シーケンスであるため、filter () の結果を強制的に計算させるには、list () 関数を使ってすべての結果を取得し、リストとして返す必要があります。
filter () の役割は、シーケンスから条件に合った要素を選び出すことです。filter () は遅延計算を使用しているため、filter () の結果を取得する時に初めて実際にフィルタリングが行われ、毎回次のフィルタリングされた要素が返されます。
1.3 sorted
ソートアルゴリズム
ソートはプログラムでよく使用されるアルゴリズムです。バブルソートやクイックソートを使用するかにかかわらず、ソートの核心は 2 つの要素の大きさを比較することです。数字であれば直接比較できますが、文字列や 2 つの dict の場合はどうでしょうか?数学的な大きさを直接比較することは意味がありません。そのため、比較の過程は関数を通じて抽象化する必要があります。
Python に内蔵されている **sorted ()** 関数はリストをソートすることができます:
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
さらに、sorted () 関数も高階関数であり、カスタムソートを実現するために key 関数を受け取ることができます。例えば、絶対値の大きさでソートする場合:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
key で指定された関数はリストの各要素に適用され、key 関数が返す結果に基づいてソートされます。元のリストと key=abs で処理されたリストを比較すると:
list = [36, 5, -12, 9, -21]
keys = [36, 5, 12, 9, 21]
その後、sorted () 関数は keys に基づいてソートし、対応する関係に従ってリストの要素を返します:
keysのソート結果 => [5, 9, 12, 21, 36]
| | | | |
最終結果 => [5, 9, -12, -21, 36]
文字列のソートの例を見てみましょう:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
デフォルトでは、文字列のソートは ASCII の大きさで比較されます。'Z' < 'a' であるため、大文字の Z は小文字の a の前に配置されます。
ここで、ソートは大文字と小文字を無視して行うべきだと提案します。このアルゴリズムを実現するためには、既存のコードを大幅に変更する必要はなく、単に key 関数を使って文字列を無視して大文字でソートすればよいのです。大文字と小文字を無視して 2 つの文字列を比較することは、実際には文字列をすべて大文字(またはすべて小文字)に変換してから比較することです。
このようにして、sorted に key 関数を渡すことで、大文字と小文字を無視したソートを実現できます:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
逆順にソートする場合は、key 関数を変更する必要はなく、第三の引数reverse=Trueを渡すことができます:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
上記の例から、高階関数の抽象能力が非常に強力であることがわかります。また、核心的なコードを非常にシンプルに保つことができます。
小結
sorted () も高階関数です。sorted () でソートする際の鍵は、マッピング関数を実装することです。
関数を返す
関数を戻り値として返す
高階関数は、関数を引数として受け取るだけでなく、関数を結果値として返すこともできます。
可変引数の合計を求める関数を実装してみましょう。通常、合計を求める関数は次のように定義されます:
def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
しかし、すぐに合計を求める必要がなく、後のコードで必要に応じて計算する場合はどうすればよいでしょうか?合計の結果を返すのではなく、合計を求める関数を返すことができます:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
lazy_sum () を呼び出すと、返されるのは合計の結果ではなく、合計を求める関数です:
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
関数 f を呼び出すと、実際に合計の結果が計算されます:
>>> f()
25
この例では、関数 lazy_sum の中に関数 sum を定義し、内部関数 sum は外部関数 lazy_sum の引数と局所変数を参照できます。lazy_sum が関数 sum を返すとき、関連する引数と変数は返された関数に保存されます。この構造は「クロージャ(Closure)」と呼ばれ、非常に強力です。
もう一点注意が必要です。lazy_sum () を呼び出すと、毎回新しい関数が返されます。同じ引数を渡してもです:
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False
f1 () と f2 () の呼び出し結果は互いに影響しません。
クロージャ
条件
1 外部関数内に内部関数が定義されている
2 外部関数に戻り値がある
3 戻り値が内部関数名である
4 内部関数が外部関数の変数を参照している
def 外部関数():
...
def 内部関数():
...
return 内部関数
戻り値の関数がその定義内部で局所変数 args を参照していることに注意してください。したがって、ある関数が別の関数を返すとき、その内部の局所変数は新しい関数によって参照され続けます。クロージャは使うのは簡単ですが、実装するのは難しいことがあります。
もう一つ注意が必要なのは、返された関数はすぐには実行されず、**f ()** が呼び出されるまで実行されないことです。
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
上の例では、毎回ループで新しい関数が作成され、作成された 3 つの関数が返されます。
あなたは f1 ()、f2 ()、f3 () の結果は 1、4、9 であるべきだと考えるかもしれませんが、実際の結果は:
>>> f1()
9
>>> f2()
9
>>> f3()
9
すべて 9 です!理由は、返された関数が変数 i を参照しているからですが、それはすぐには実行されません。3 つの関数が返されたとき、参照している変数 i はすでに 3 になっているため、最終的な結果は 9 になります。
クロージャを返すときは、返す関数がループ変数や後で変更される可能性のある変数を参照しないように注意してください。
もしループ変数を参照する必要がある場合は、別の関数を作成し、その関数の引数にループ変数の現在の値をバインドする方法があります。これにより、ループ変数が後でどのように変更されても、関数パラメータにバインドされた値は変わりません:
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)はすぐに実行されるため、iの現在の値がf()に渡される
return fs
結果を再確認しましょう:
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
欠点はコードが長くなることですが、lambda 関数を使ってコードを短縮することができます。
nonlocal
クロージャを使用すると、内側の関数が外側の関数の局所変数を参照します。外側の変数の値を読むだけであれば、返されたクロージャ関数は正常に動作します:
def inc():
x = 0
def fn():
# xの値を読むだけ:
return x + 1
return fn
f = inc()
print(f()) # 1
print(f()) # 1
しかし、外側の変数に値を代入すると、Python インタプリタは x を関数 fn () の局所変数として扱うため、エラーが発生します:
エラー
def inc():
x = 0
def fn():
# nonlocal x
x = x + 1
return x
return fn
f = inc()
print(f()) # 1
print(f()) # 2
理由は、x が局所変数として初期化されていないため、直接 x+1 を計算することはできません。しかし、実際には inc () 関数内部の x を参照したいので、fn () 関数内部に nonlocal x の宣言を追加する必要があります。この宣言を追加すると、インタプリタは fn () の x を外側関数の局所変数として認識し、すでに初期化されているため、x+1 を正しく計算できます。
クロージャを使用する際、外側の変数に値を代入する前に、nonlocalを使用してその変数が現在の関数の局所変数ではないことを宣言する必要があります。
小結
1 関数は計算結果を返すことも、関数を返すこともできます。
2 関数を返すときは、その関数が実行されていないことを忘れずに、返された関数内で変更される可能性のある変数を参照しないようにしてください。
匿名関数
関数を渡す際に、明示的に関数を定義する必要がない場合、匿名関数を直接渡す方が便利です。
Python では、匿名関数に対して限られたサポートが提供されています。map () 関数の例を再度挙げると、f (x)=x^2 を計算する際、f (x) 関数を定義する代わりに、匿名関数を直接渡すことができます:
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
比較すると、匿名関数 lambda x: x * x は実際には次のようになります:
def f(x):
return x * x
キーワード lambda は匿名関数を示し、コロンの前の x は関数の引数を示します。
匿名関数には制限があります。1 つの式しか持てず、return を書く必要はなく、戻り値はその式の結果です。
匿名関数を使用する利点は、関数に名前がないため、関数名の衝突を心配する必要がないことです。さらに、匿名関数も関数オブジェクトであり、匿名関数を変数に代入して、その変数を使って関数を呼び出すこともできます:
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
同様に、匿名関数を戻り値として返すこともできます。例えば:
def build(x, y):
return lambda: x * x + y * y
Python は匿名関数に対して限られたサポートを提供しており、簡単な場合にのみ使用できます。
デコレーター
特徴
1 関数 A が引数として現れる(関数 B が関数 A を引数として受け取る)
2 クロージャの特徴を持つ必要がある
# デコレーターを定義
def decorate(func):
a = 100
print('wrapper外層の印刷テスト')
def wrapper():
func()
print('--------->ペイント')
print('--------->床を敷く', a)
print('--------->ドアを取り付ける')
print('wrapperの読み込みが完了しました......')
return wrapper
# デコレーターを使用
@decorate
def house():
print('私はスケルトンの家です....')
'''
デフォルトの実行:
1. houseは装飾される関数です。
2. 装飾される関数はデコレーターdecorateに引数として渡されます。
3. decorate関数が実行されます。
4. 戻り値が再びhouseに代入されます。
'''
print(house)
house() # wrapper()
# def house1():
# house()
# print('ペイント')
# print('床を敷く')
# 関数houseを呼び出す
# house()
関数もオブジェクトであり、関数オブジェクトは変数に代入できるため、変数を通じてその関数を呼び出すこともできます。
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
関数オブジェクトにはname属性(注意:前後にそれぞれ 2 つのアンダースコアがあります)があり、関数の名前を取得できます:
>>> now.__name__
'now'
>>> f.__name__
'now'
さて、now () 関数の機能を強化したいとします。例えば、関数呼び出しの前後に自動的にログを印刷したいが、now () 関数の定義を変更したくない場合、このようにコードの実行中に動的に機能を追加する方法を「デコレーター」(Decorator)と呼びます。
本質的に、デコレーターは関数を返す高階関数です。
多層デコレーター
デコレーターが多層の場合、関数に最も近いものが優先されます。
# デコレーター
def zhuang1(func):
print('------->1 開始')
def wrapper(*args, **kwargs):
func()
print('ペイント')
print('------->1 終了')
return wrapper
def zhuang2(func):# func=house
print('------->2 開始')
def wrapper(*args, **kwargs):
func()
print('床を敷き、ドアを取り付ける.....')
print('------->2 終了')
return wrapper
@zhuang2
@zhuang1
def house(): # house = wrapper
print('私はスケルトンの家です.....')
house()
出力
------->1 開始
------->1 終了
------->2 開始
------->1 終了
私はスケルトンの家です.....
ペイント
床を敷き、ドアを取り付ける.....
引数付きデコレーター
'''
引数付きデコレーターは3 層です。
最外層の関数がデコレーターの引数を受け取る役割を果たします。
内部の内容は元のデコレーターの内容です。
# 引数付きデコレーター
def outer(a): # 第一層:デコレーターの引数を受け取る
print('------------1 開始')
def decorate(func): # 第二層:関数を受け取る
print('------------2 開始')
def wrapper(*args, **kwargs): # 第三層:関数の引数を受け取る
func(*args)
print("---->タイルを敷く{}枚".format(a))
print('------------2 終了')
return wrapper # 返されるのは第三層
print('------------1 終了')
return decorate # 返されるのは第二層
@outer(10)
def house(time):
print('私は{}日に家の鍵を受け取りました、スケルトンの家です....'.format(time))
# @outer(100)
# def street():
# print('新しく修理された通りの名前は:黒泉路')
house('2019-6-12')
# street()
オブジェクト指向(OOP)のデザインパターンでは、デコレーターは装飾パターンと呼ばれます。OOP の装飾パターンは、継承と組み合わせを通じて実現されますが、Python は OOP のデコレーターをサポートするだけでなく、文法レベルでデコレーターを直接サポートします。Python のデコレーターは関数で実装することも、クラスで実装することもできます。
偏関数
Python のfunctoolsモジュールは多くの便利な機能を提供しており、その中の一つが偏関数(Partial function)です。ここでの偏関数は数学的な意味での偏関数とは異なることに注意してください。
関数の引数を紹介する際に、デフォルト値を設定することで関数呼び出しの難易度を下げることができると説明しました。偏関数も同様のことができます。以下に例を示します:
int () 関数は文字列を整数に変換できますが、文字列のみを渡すと、int () 関数はデフォルトで 10 進数として変換します:
>>> int('12345')
12345
しかし、int () 関数は追加の base 引数を提供しており、デフォルト値は 10 です。base 引数を渡すと、N 進数の変換が可能になります:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
大量の 2 進数文字列を変換する必要がある場合、毎回 int (x, base=2) を渡すのは非常に面倒です。そこで、base=2 をデフォルトで渡す int2 () 関数を定義することを考えます:
def int2(x, base=2):
return int(x, base)
これにより、2 進数を変換するのが非常に便利になります:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial は偏関数を作成するのを助けるもので、int2 () を自分で定義する必要はなく、次のコードを使って新しい関数 int2 を作成できます:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
したがって、functools.partial の作用を簡単にまとめると、関数の一部の引数を固定し(つまりデフォルト値を設定し)、新しい関数を返すことです。この新しい関数を呼び出すと、より簡単になります。
上記の新しい int2 関数は、単に base 引数を 2 に再設定しただけですが、関数呼び出し時に他の値を渡すこともできます:
>>> int2('1000000', base=10)
1000000
最後に、偏関数を作成する際、実際には関数オブジェクト、*args、**kw の 3 つの引数を受け取ることができます。次のように渡すと:
int2 = functools.partial(int, base=2)
実際には int () 関数のキーワード引数 base を固定したことになります。つまり:
int2('10010')
は次のようになります:
kw = { 'base': 2 }
int('10010', **kw)
渡すと:
max2 = functools.partial(max, 10)
実際には 10 を * args の一部として自動的に左側に追加します。つまり:
max2(5, 6, 7)
は次のようになります:
args = (10, 5, 6, 7)
max(*args)
結果は 10 になります。
小結
関数の引数が多すぎて簡略化が必要な場合、functools.partial を使用して新しい関数を作成できます。この新しい関数は、元の関数の一部の引数を固定し、呼び出し時により簡単になります。
六 モジュール#
Python では、1 つの.py ファイルをモジュール(Module)と呼びます。
モジュールを使用する利点は何でしょうか?
1 最大の利点は、コードの保守性を大幅に向上させることです。次に、コードを書く際にゼロから始める必要がありません。モジュールが完成すれば、他の場所で参照できます。プログラムを書くとき、私たちは Python の内蔵モジュールやサードパーティのモジュールを頻繁に参照します。
2 モジュールを使用することで、関数名や変数名の衝突を避けることができます。同じ名前の関数や変数は、異なるモジュールに完全に存在することができるため、自分でモジュールを書くときに他のモジュールと名前が衝突することを心配する必要はありません。ただし、内蔵関数名と衝突しないように注意する必要があります。
異なる人が作成したモジュール名が同じ場合はどうなるでしょうか?モジュール名の衝突を避けるために、Python はディレクトリによってモジュールを整理する方法を導入しました。これをパッケージ(Package)と呼びます。
例えば、abc.py というファイルは abc という名前のモジュールであり、xyz.py というファイルは xyz という名前のモジュールです。abc と xyz という 2 つのモジュール名が他のモジュールと衝突した場合、パッケージを使ってモジュールを整理し、衝突を避けることができます。方法は、トップレベルのパッケージ名を選択し、次のようにディレクトリを保存します:
mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py
パッケージを導入すると、トップレベルのパッケージ名が他のものと衝突しない限り、すべてのモジュールは他のものと衝突しません。現在、abc.py モジュールの名前は mycompany.abc に変わり、同様に xyz.py のモジュール名は mycompany.xyz に変わります。
mycompany
├─ web
│ ├─ __init__.py
│ ├─ utils.py
│ └─ www.py
├─ __init__.py
├─ abc.py
└─ utils.py
ファイル www.py のモジュール名は mycompany.web.www であり、2 つのファイル utils.py のモジュール名はそれぞれ mycompany.utils と mycompany.web.utils です。
mycompany.web もモジュールです。このモジュールに対応する.py ファイルを指摘してください。
!!モジュールを作成する際は、命名に注意し、Python の組み込みモジュール名と衝突しないようにしてください。例えば、システムには sys モジュールが組み込まれているため、自分のモジュールを sys.py と名付けることはできません。そうしないと、システムの sys モジュールをインポートできなくなります。
まとめ
1 モジュールは Python コードの集合であり、他のモジュールを使用したり、他のモジュールに使用されたりすることができます。
2 自分のモジュールを作成する際は、以下に注意してください:
2.1 モジュール名は Python の変数命名規則に従い、中国語や特殊文字を使用しないこと;
2.2 モジュール名はシステムモジュール名と衝突しないようにし、まずシステムにそのモジュールが存在するか確認すること。確認方法は、Python インタラクティブ環境で import abc を実行し、成功すればそのモジュールがシステムに存在することを示します。
モジュールの使用
Python 自体には非常に便利なモジュールが多数内蔵されており、インストールが完了すれば、これらのモジュールをすぐに使用できます。
内蔵の sys モジュールを例にとり、hello モジュールを作成します:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' テストモジュール '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args)==1:
print('こんにちは、世界!')
elif len(args)==2:
print('こんにちは、%s!' % args[1])
else:
print('引数が多すぎます!')
if __name__=='__main__':
test()
1 行目と 2 行目は標準コメントで、1 行目のコメントはこの hello.py ファイルを Unix/Linux/Mac 上で直接実行できるようにし、2 行目のコメントは.py ファイル自体が標準 UTF-8 エンコーディングを使用していることを示します;
4 行目は文字列で、モジュールのドキュメントコメントを示します。モジュールコードの最初の文字列はモジュールのドキュメントコメントと見なされます;
6 行目では__author__変数を使用して著者を記入します。これにより、ソースコードを公開したときに他の人があなたの名前を知ることができます;
以上が Python モジュールの標準ファイルテンプレートですが、もちろんすべて削除して書かないこともできますが、標準に従うことは間違いありません。
その後が実際のコード部分です。
sys モジュールを使用する最初のステップは、このモジュールをインポートすることです:
import sys
sys モジュールをインポートすると、変数 sys がそのモジュールを指します。この変数を利用して、sys モジュールのすべての機能にアクセスできます。
sys モジュールにはargv 変数があり、コマンドラインのすべての引数をリストで保存しています。argv には少なくとも 1 つの要素が含まれています。なぜなら、最初の引数は常にその.py ファイルの名前だからです。
例えば:
python3 hello.py を実行すると、sys.argv は ['hello.py'] になります;
python3 hello.py Michael を実行すると、sys.argv は ['hello.py', 'Michael'] になります。
最後に、次の 2 行のコードに注意してください:
if __name__=='__main__':
test()
コマンドラインで hello モジュールファイルを実行すると、Python インタプリタは特殊変数__name__を__main__に設定しますが、他の場所でこの hello モジュールをインポートすると、if 判断は失敗します。したがって、この if テストにより、モジュールがコマンドラインで実行されるときに追加のコードを実行できるようになります。最も一般的なのはテストを実行することです。
コマンドラインで hello.py を実行してみましょう:
$ python3 hello.py
こんにちは、世界!
$ python hello.py Michael
こんにちは、Michael!
Python インタラクティブ環境を起動し、hello モジュールをインポートすると:
$ python3
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 23 2015, 02:52:03)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>>
インポート時には、Hello, world! は印刷されません。なぜなら、test () 関数は実行されていないからです。
hello.test () を呼び出すと、Hello, world! が印刷されます:
>>> hello.test()
こんにちは、世界!
スコープ
モジュール内で多くの関数や変数を定義することがありますが、他の人に使用してほしい関数や変数もあれば、モジュール内部でのみ使用したい関数や変数もあります。Python では、_プレフィックスを使用してこれを実現します。
1 通常の関数や変数名は公開されており(public)、直接参照できます。例えば:abc、x123、PI など;
2 xxx のような変数は特殊変数であり、直接参照できますが、特殊な用途があります。例えば、上記の__author、__name__は特殊変数であり、hello モジュールが定義したドキュメントコメントも特殊変数__doc__でアクセスできます。私たち自身の変数にはこのような変数名を使用しない方が良いです;
3 _xxx や__xxx のような関数や変数は非公開(private)であり、直接参照すべきではありません。例えば_abc、__abc など;
私たちが言うように、private 関数や変数は「直接参照すべきではない」とされているのは、Python には private 関数や変数へのアクセスを完全に制限する方法がないからです。しかし、プログラミングの習慣として、private 関数や変数を参照すべきではありません。
private 関数や変数が他の人に参照されないべきであれば、それらにはどのような意味があるのでしょうか?以下の例を見てみましょう:
def _private_1(name):
return 'こんにちは、%s' % name
def _private_2(name):
return 'やあ、%s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
モジュール内で greeting () 関数を公開し、内部ロジックを private 関数で隠すことができます。これにより、greeting () 関数を呼び出す際に内部の private 関数の詳細を気にする必要がなくなります。これは非常に有用なコードのカプセル化と抽象化の方法です。つまり、外部で参照する必要のない関数はすべて private として定義し、外部で参照する必要のある関数だけを public として定義するということです。
サードパーティモジュールのインストール
Python では、サードパーティモジュールをインストールするためにパッケージ管理ツールpipを使用します。
一般的に、サードパーティライブラリは Python 公式の pypi.python.org サイトに登録されています。サードパーティライブラリをインストールするには、そのライブラリの名前を知っている必要があります。公式サイトや pypi で検索できます。例えば、Pillow の名前は Pillow です。したがって、Pillow をインストールするコマンドは次のようになります:
pip install Pillow
モジュール検索パス
モジュールを読み込もうとすると、Python は指定されたパスで対応する.py ファイルを検索します。見つからない場合はエラーが発生します:
>>> import mymodule
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named mymodule
デフォルトでは、Python インタプリタは現在のディレクトリ、すべてのインストール済みの内蔵モジュールおよびサードパーティモジュールを検索します。検索パスは sys モジュールの path 変数に保存されています:
>>> import sys
>>> sys.path
自分の検索ディレクトリを追加する必要がある場合、2 つの方法があります:
1 つは sys.path を直接変更し、検索したいディレクトリを追加することです:
>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')
この方法は実行時に変更され、実行が終了すると無効になります。
もう 1 つは、環境変数 PYTHONPATH を設定することです。この環境変数の内容は自動的にモジュール検索パスに追加されます。設定方法は Path 環境変数を設定するのと似ています。注意点は、自分の検索パスだけを追加し、Python 自身の検索パスには影響を与えないことです。