Python は初心者に優しい言語です。ただし、デコレータなど、使いこなすのが難しい高度な機能も数多くあります。多くの初心者は、デコレータとその仕組みを理解したことがありません。この記事では、デコレータの詳細を紹介します。
Python では、関数は非常に柔軟な構造であり、変数に代入したり、パラメータとして別の関数に渡したり、関数の出力として使用したりできます。デコレーターは本質的に、他の関数が変更せずに機能を追加できるようにする関数です。
これは「装飾」の意味です。この「装飾」自体が機能を表します。別の機能を変更するために使用される場合、その機能をそれらの機能に追加します。
一般的に、デコレータが提供する @ 構文シュガー (Syntactic Sugar) を使用して、他の関数やオブジェクトを装飾できます。以下に示すように、@dec デコレータを使用して関数 func () を装飾します:
@dec def func(): pass
デコレータを理解する最良の方法は、デコレータがどのような問題を解決するかを理解することです。この記事では、デコレータを段階的に紹介します。特定の問題から出発し、その優雅さと力を誇示します。
デコレータの目的を理解するために、簡単な例を見てみましょう。 2 番目のパラメーターのデフォルト値が 10 である単純な加算関数 dec.py があるとします。
# dec.py def add(x, y=10): return x + y
この加算関数を詳しく見てみましょう:
>>> add(10, 20) 30 >>> add <function add at 0x7fce0da2fe18> >>> add.__name__ 'add' >>> add.__module__ '__main__' >>> add.__defaults__ # default value of the `add` function (10,) >>> add.__code__.co_varnames # the variable names of the `add` function ('x', 'y')
必要はありません。これらが何であるかを理解するには、Python のすべての関数がオブジェクトであり、さまざまなプロパティとメソッドがあることを覚えておいてください。また、検査モジュールを使用して add() 関数のソース コードを表示することもできます:
>>> from inspect import getsource >>> print(getsource(add)) def add(x, y=10): return x + y
ここで、何らかの方法で加算関数を使用します。たとえば、いくつかの操作を使用して関数をテストします:
# dec.py from time import time def add(x, y=10): return x + y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) Output: i add(10) 20 add(20, 30) 50 add("a", "b") ab
各操作の時間を知りたい場合は、time モジュールを呼び出すことができます:
# dec.py from time import time def add(x, y=10): return x + y before = time() print('add(10)', add(10)) after = time() print('time taken: ', after - before) before = time() print('add(20, 30)', add(20, 30)) after = time() print('time taken: ', after - before) before = time() print('add("a", "b")', add("a", "b")) after = time() print('time taken: ', after - before) Output: add(10) 20 time taken:6.699562072753906e-05 add(20, 30) 50 time taken:6.9141387939453125e-06 add("a", "b") ab time taken:6.9141387939453125e-06
さて、プログラマとして、少しかゆいところはありませんか? 結局のところ、私たちはコピーしたくないのです。常に同じコードを貼り付けます。現在のコードはあまり読みにくくなっています。何かを変更したい場合は、表示されている箇所をすべて変更する必要があります。Python にはもっと良い方法があるはずです。
次のように、追加関数で実行時間を直接取得できます。
# dec.py from time import time def add(x, y=10): before = time() rv = x + y after = time() print('time taken: ', after - before) return rv print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b"))
この方法は、前の方法よりも明らかに優れています。しかし、別の機能がある場合、これは不便に思われます。複数の関数がある場合:
# dec.py from time import time def add(x, y=10): before = time() rv = x + y after = time() print('time taken: ', after - before) return rv def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
add と sub は両方とも関数であるため、これを利用してタイマー関数を作成できます。関数の操作時間を計算するタイマーが必要です:
def timer(func, x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv
これは便利ですが、次のようにタイマー関数を使用してさまざまな関数をラップする必要があります:
print('add(10)', timer(add,10)))
デフォルト値はまだ 10 ですか?必ずしも。では、どうすればもっと良くなるでしょうか?
ここにアイデアがあります: 新しいタイマー関数を作成し、他の関数をラップし、ラップされた関数を返します:
def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f
あとは、add を timer And sub 関数でラップするだけです:
add = timer(add)
以上です!完全なコードは次のとおりです。
# dec.py from time import time def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f def add(x, y=10): return x + y add = timer(add) def sub(x, y=10): return x - y sub = timer(sub) print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30)) Output: time taken:0.0 add(10) 20 time taken:9.5367431640625e-07 add(20, 30) 50 time taken:0.0 add("a", "b") ab time taken:9.5367431640625e-07 sub(10) 0 time taken:9.5367431640625e-07 sub(20, 30) -10
プロセスを要約しましょう。関数 (add 関数など) があり、その関数をアクション (タイミングなど) でラップします。パッケージ化の結果は、特定の新しい機能を実装できる新しい機能です。
もちろん、デフォルト値にはまだ問題があるため、後で修正します。
さて、上記のソリューションはデコレータのアイデアに非常に近いものです。一般的な動作を使用して特定の関数をラップします。このパターンはデコレーションです。 . デバイスが何をしているのか。デコレータを使用した後のコードは次のとおりです。
def add(x, y=10): return x + y add = timer(add) You write: @timer def add(x, y=10): return x + y
これらの機能は同じであり、これは Python デコレータの機能です。実装する関数は add = timer(add) に似ていますが、デコレーターが構文を関数の上に置き、構文が @timer より単純である点が異なります。
# dec.py from time import time def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f @timer def add(x, y=10): return x + y @timer def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
ここで、解決されていない小さな問題がまだあります。タイマー関数では、パラメーター x と y をハードコーディングします。つまり、y のデフォルト値を 10 に指定します。引数とキーワード引数を関数に渡す方法 (*args と **kwargs) があります。パラメータは関数の標準パラメータ (この場合は x がパラメータ)、キーワード パラメータはすでにデフォルト値 (この場合は y=10) を持つパラメータです。コードは次のとおりです。
# dec.py from time import time def timer(func): def f(*args, **kwargs): before = time() rv = func(*args, **kwargs) after = time() print('time taken: ', after - before) return rv return f @timer def add(x, y=10): return x + y @timer def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
これで、タイマー関数は、これらのパラメータを関数に渡すだけなので、任意の関数、任意のパラメータ、および任意のデフォルト値設定を処理できます。
関数を別の関数でラップして便利な動作を追加できれば、さらに一歩進められるのではないかと疑問に思うかもしれません。関数を別の関数でラップし、また別の関数によってラップされるのでしょうか? #########できる!実際、関数は必要に応じて深くすることができます。たとえば、関数を n 回実行するデコレーターを作成するとします。以下に示すように:
def ntimes(n): def inner(f): def wrapper(*args, **kwargs): for _ in range(n): rv = f(*args, **kwargs) return rv return wrapper return inner
@ntimes(3) def add(x, y): print(x + y) return x + y
以上がPython デコレータを 1 つの記事で理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。