5.4 関数の特別な使用法
5.4.1 匿名関数
いわゆる無名関数は、def ステートメントなどの標準的な形式で定義された関数です。キーワードを Python で使用しlambda
て、無名関数を作成できます。ラムダで作成された無名関数の関数本体はdef
、定義された関数の本体よりも単純です。構文は次のとおりです。
ラムダ [パラメータ 1[,パラメータ 2],...パラメータ n]]: 式
lam_sum = lambda arg1, arg2: arg1 + arg2
print(lam_sum(10, 20))
30
上記のコードでは、最初の行で 2 つの数値の合計演算を実行するラムダ関数を定義し、ラムダ関数に lam_sum という名前を付けています。次に、lam_sum() 関数を使用して合計関数を実装します。
Lambda によって作成された匿名関数にカプセル化できるロジックは限られています。
ラムダ関数には独自の名前空間があり、独自のパラメーター リスト外またはグローバル名前空間内のパラメーターにアクセスすることはできません。
実際、通常、匿名関数を使用する場合、作成された匿名関数には名前が付けられません。これは無名関数の単純さを失うためです。一部のシナリオでは、関数を渡す必要があり、必要なロジックはそれほど複雑ではありません。しかし、別のものを作成したくないので、現時点では無名関数を直接使用できます。次のように:
print(list(map(lambda x: x * x, [1, 2, 3, 4, 5])))
[1、4、9、16、25]
5.4.2 再帰呼び出し
Python で関数を定義する場合、関数本体は他の関数を呼び出すことも、それ自体を呼び出すこともできます。このように自分自身を呼び出す方法は、再帰呼び出しと呼ばれます。再帰関数の定義は次のとおりです。
def recursion():
return recursion()
明らかに、上で定義した関数を実行すると、しばらく実行するとプログラムがクラッシュする (例外がスローされる) ことがわかります。
理論的には、このプログラムは永久に実行されますが、関数が呼び出されるたびにメモリが消費されます。したがって、関数呼び出しの数が特定のレベルに達した後 (および以前の関数呼び出しが返されなかった場合)、すべてのメモリ領域が使い果たされ、プログラムが終了してエラー メッセージが表示されます。回)」。
最大再帰深度は、次のコードで変更できます。
import sys
sys.setrecursionlimit(99999)
この関数の再帰は、無限再帰と呼ばれます (while True で始まり、break ステートメントと return ステートメントを含まないループが無限ループと呼ばれるのと同じように) 理論的には終了しないためです。必要なのは、あなたを助けることができる再帰関数であり、そのような再帰関数は通常、次の 2 つの部分で構成されます。
ベースライン条件: この条件が満たされた場合、関数は値を直接返します。
再帰条件: 問題の一部を解決するための 1 つ以上の呼び出しが含まれています。
ここで重要なのは、問題を小さな部分に分割することで、無限の再帰を回避することです。これは、問題が最終的にベースライン条件下で解決できる最小の問題に分解されるためです。
では、関数呼び出し自体をどのように行うのでしょうか? これは、見た目ほど理解するのは難しくありません。前述したように、関数が呼び出されるたびに、新しい名前空間が作成されます。これは、関数がそれ自体を呼び出すとき、2 つの異なる関数 (より正確には、同じ関数の異なるバージョン (つまり、異なる名前空間)) が通信していることを意味します。これは、同じ種の 2 匹の動物が互いに通信していると考えることができます。
递归示例1:通过递归的方式求一个数的阶乘
def factorial(p_int=0):
if p_int == 0: # 基线条件
return 1
else: # 递归条件
return p_int * factorial(p_int - 1)
print(factorial(10))
3628800
递归示例2:通过递归的方式求幂
def power(x, n):
return 1 if n == 0 else x * power(x, n - 1)
print(power(2, 10))
1024
递归示例3:通过递归的方式解决汉诺塔问题
def move(n, a='A', b='B', c='C'):
if n == 1:
print('移动', a, '-->', c)
else:
move(n - 1, a, c, b)
move(1, a, b, c)
move(n - 1, b, a, c)
move(4)
移動 A --> B
移動 A --> C
移動 B --> C
移動 A --> B
移動 C --> A
移動 C --> B
移動 A --> B
移動 A --> C
移動 B --> C
が B を移動 --> A
が C を移動 --> A
が B を移動 --> C
が A を移動 --> B
が A を移動 --> C
が B を移動 --> C
いくつかの特別な問題では、通常のループで実現できますが、コードは再帰を使用した方が簡単です。ロジックもより明確になります。
理論的には、すべての再帰関数はループとして記述できますが、ループのロジックは再帰のロジックほど明確ではありません。
再帰関数を使用する場合は、スタック オーバーフローを防ぐように注意する必要があります。コンピュータでは関数呼び出しはスタックと呼ばれるデータ構造を介して実装されており、関数呼び出しが入るたびにスタックにスタック フレームが追加され、関数が戻るたびにスタックがスタック フレームごとに減らされます。 . スタックのサイズは無限ではないため、再帰呼び出しが多すぎるとスタックがオーバーフローします。
5.4.3 部分関数
参考:関数の一部 関数
のパラメータを導入する際に、パラメータのデフォルト値を設定することで、関数呼び出しの難易度を下げることができると述べました。部分関数でも同じことができます。
int() 関数は文字列を整数に変換できます. 文字列のみが渡された場合, int() 関数はデフォルトで 10 進数で変換します:
>>> int('12345')
12345
ただし、int() 関数は追加の基本パラメーターも提供します。デフォルト値は 10 です。base パラメータを渡すと、N-ary 変換を行うことができます:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
大量のバイナリ文字列を変換する必要があると仮定すると、毎回 int(x, base=2) を渡すのは非常に面倒なので、int2() の関数を定義して、base=2 を渡すことができると考えました。デフォルトでは:
def int2(x, base=2):
return int(x, base)
このように、バイナリを変換することは非常に便利です。
>>> 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
最後に、部分関数を作成するとき、関数オブジェクトの 3 つのパラメーター、*args および **kw を実際に受け取ることができます。渡すとき:
>>> int2 = functools.partial(int, base=2
) ( ) 関数のキーワード パラメータ ベース。つまり、
>>> int2('10010')
は次と同等です:
>>> kw = { 'base': 2 }
>>> int('10010', **kw)
が渡された場合:
>>> max2 = functools.partial ( max, 10)
は実際には *args の一部として左に 10 を自動的に追加します。つまり:
>>> max2(5, 6, 7)
は次と同等です:
>>> args = (10, 5, 6, 7)
>>> max(*args)
10
関数のパラメーターが多すぎて単純化する必要がある場合は、 functools.partial を使用して新しい関数を作成します. この新しい関数は、元の関数のパラメーターの一部を修正して、呼び出しやすくすることができます.
5.4.4 閉鎖
クロージャは一種の関数の入れ子です. 最初に関数が定義され、 と呼ばれ外部函数
ます. この外部関数本体でもう 1 つ定義し、この内部関数本体で内部函数
外部関数の変数を使用します。外部関数は最終的に内部関数を返します。次に、外部関数と内部関数は、クロージャーと呼ばれる特別なオブジェクトを構成します。
クロージャーはグローバル変数の使用を回避するため、関数の外でローカル変数にアクセスできるようになります. オブジェクト指向と比較して、余分なメソッドを継承する必要がなく、クロージャーはスペースを占有しません.
闭包示例
a = 1
def out_fun(b):
c = 3
def in_fun(d):
print(a + b + c + d)
return in_fun
infun = out_fun(2)
infun(4)
10
in_fun
グローバル変数 a、外部関数out_fun
のローカル変数 c 、パラメータ b が内部関数でアクセスされていることがわかります。
5.4.4.2 デコレータ
デコレータの本質: 関数クロージャのシンタックス シュガー。機能を装飾することで、機能の機能を強化したり、元の機能にない機能を追加したりできます。
デコレータは、最初に呼び出されたときに一度だけ装飾された関数を拡張します。
簡単な関数から始めましょう。ここで、1 から 100 までの累積和を計算し、結果を出力する関数があるとします。計算が速すぎるのを避けるために、ループ累積を使用するときに 0.01 秒待機するように設定しました。関数の定義は次のとおりです。
def mysum1():
from time import sleep
total = 0
for _ in range(101):
sleep(0.01)
total += _
print(total)
mysum1()
5050
この時点で、この関数の実行にかかる時間を知りたい場合は、実行前後の時間を取得して計算するか、関数を変更して内部関数本体の前後の時間を取得することができます。関数。しかし、これら 2 つの方法は扱いにくく、特に 2 番目の方法では、関数コードの侵入的な変更が必要になります。
このとき、関数にデコレータを追加することで実現できます。デコレータを作成する一般的な方法は次のとおりです。
装饰器的简单定义
def decorator1(func):
def inner():
print('在这里执行被装饰函数执行前的增强操作')
func() # 执行被装饰的函数
print('在这里执行被装饰函数执行前的增强操作')
return inner
上記のように、デコレータも関数です。デコレーターが受け取ったパラメーターがデコレートされる関数であることだけです。次に、デコレータ内に関数を定義すると、内部関数本体が拡張する操作コードを実行し、装飾された関数を実行します。最後に、内部関数を返します。
次のステップは、関数をデコレータで装飾することです。上で定義した mysum1 関数で装飾します。
装饰器的使用
@decorator1
def mysum1():
from time import sleep
total = 0
for _ in range(101):
sleep(0.01)
total += _
print(total)
mysum1()
ここで装飾関数を実行する前に強化操作を実行します
5050
ここで装飾関数を実行する前に強化操作を実行します
上記からわかるように、関数を装飾したい場合は、関数を定義するときに def ステートメントの上の行に追加するだけです@装饰器函数
。
デコレーターが関数を装飾する場合:
@decorator
def myfun():
print("hello")
上記のコードは次と同等です。
def myfun():
print("hello")
myfun = decorator(myfun)
デコレーターが関数を装飾すると、関数の機能が強化されます。これは、この関数が呼び出されると、デコレーター関数が定義されているときに実際に内部関数を呼び出すためです。このとき、内部機能は拡張機能コマンドと独自の装飾機能で構成されます。
创建一个统计函数运行时长的装饰器
import time
def decorator1(func):
def inner():
begin = time.time()
func() # 执行被装饰的函数
end = time.time()
print(f"函数`{
func.__name__}`运行的总时间为:{
end - begin:.3} 秒")
return inner
@decorator1
def mysum1():
from time import sleep
total = 0
for _ in range(101):
sleep(0.01)
total += _
print(total)
mysum1()
5050
機能の合計mysum1
実行時間: 1.59 秒
5.4.4.2.2 装飾された関数がパラメーターを受け取る
上記の例ではdecorator
、decorator 関数で装飾された関数は入力パラメーターを持つことができず、実際の使用ではあまり便利ではありません。
これは、デコレータを改造して、デコレータ機能をより広く利用できるようにすることで回避できます。
装饰器定义:让被装饰函数接收参数
import time
def decorator2(func):
def inner(*args, **kwargs):
begin = time.time()
func(*args, **kwargs) # 执行被装饰的函数
end = time.time()
print(f"函数`{
func.__name__}`运行的总时间为:{
end - begin:.3}")
return inner
修正が必要な箇所:
1.定義時decorator
にデコレータ関数の内部関数にinner
コレクション位置パラメータとコレクションキーワードパラメータを追加
2.デコレータ関数decorator
の内部inner
関数本体で、デコレータでfunc
装飾された関数を実行する際に、パラメータをアンパックします。
装饰带有参数的函数:
@decorator2
def mysum2(a, b):
from time import sleep
total = a
for _ in range(total + 1, b + 1):
sleep(0.01)
total += _
print(total)
mysum2(1, 100)
5050
機能の合計mysum1
実行時間は 1.56 です。
5.4.4.2.3 デコレータ関数がパラメータを受け取る
上記のデコレーターを変更することで、装飾された関数はパラメーターを入力できます。上記のデコレーター関数decorator2
は、装飾された関数の実行時間を計算できますが、実行時間を取得できるのは 1 回だけです。パラメータを介していつでも実行時間を取得したい場合は、デコレータがパラメータを受信できるようにする必要があります。
装饰器定义:装饰器接收参数
import time
def decorator3(n):
def inner(func):
def wrapper(*args, **kwargs):
begin = time.time()
for _ in range(iteration):
func(*args, **kwargs)
end = time.time()
print(f"函数`{
func.__name__}`运行的总时间为:{
end - begin:.3}")
return wrapper
return inner
改善が必要な点:
1. デコレータ関数は、この時点でデコレートされた関数にパラメータを渡しませんが、デコレータ自体のパラメータを定義します. デモンストレーションでは、位置パラメータ n が使用されています. future キーワード パラメータの場合、*args、**kwargs などを使用してパラメータを収集することもできます。
2. 内部関数はinner
、装飾された関数を収集するために使用されます。
3. 内部関数inner
の内部関数はwrapper
、装飾された関数のパラメーターを収集するために使用されます。そして、強化が必要なコマンドを書きます。最後に、装飾された関数を実行することは、実際にはこのwrapper
関数を実行することです。
装饰器接收参数:
import time
def decorator3(n):
def inner(func):
def wrapper(*args, **kwargs):
begin = time.time()
for _ in range(n):
func(*args, **kwargs)
end = time.time()
print(f"函数`{
func.__name__}`运行的总时间为:{
end - begin:.3}")
return wrapper
return inner
@decorator3(10)
def mysum3(a, b):
from time import sleep
total = a
for _ in range(total + 1, b + 1):
sleep(0.01)
total += _
print(total)
mysum3(1, 10)
# 等价于:mysum3 = decorator3(10)(mysum3)
55
55
55
55
55
55 55
55
55
55
関数
の合計mysum3
実行時間: 1.41
5.4.4.2.4 デコレータの戻り値
wrapper
前節の内容を理解すれば、 return が関数内にある限り、装飾された関数の戻り値であると考えるのは簡単です。
装饰器定义:接收被装饰函数的返回值
import time
def decorator3(n):
def inner(func):
def wrapper(*args, **kwargs):
begin = time.time()
for _ in range(n):
func(*args, **kwargs)
end = time.time()
print(f"函数`{
func.__name__}`运行的总时间为:{
end - begin:.3}")
return end - begin
return wrapper
return inner
1. 上記のコードでは、return end – begin がデコレーターの戻り値です。変数経由で受け取ることができます。
@decorator3(3)
def mysum3(a, b):
from time import sleep
total = a
for _ in range(total + 1, b + 1):
sleep(0.01)
total += _
print(total)
total_time = mysum3(1, 10)
print(total_time)
関数の
mysum3
実行にかかった合計時間: 1.42
1.4218323230743408
5.4.4.2.5 複数のデコレーターが同じ関数をデコレートする
関数の場合、複数のデコレータを使用してそれを装飾できます。次のように記述します。
@decorator1
@decorator2
def 被装饰函数():
pass
複数のデコレーターによって装飾された関数の場合、装飾の順序は最も近いものから遠いものへとなります。つまり、decorator2 が最初に装飾され、次に decorator1 が装飾されます。