[転載]Python装飾器の最も率直な理解
21984 ワード
https://www.runoob.com/w3cnote/python-func-decorators.html一番下に引いてコメントを見て以下は上記のリンクから写します
初級知識.
Pythonの装飾器について話す前に、まず例を挙げたいと思います.少し汚れていますが、装飾器という話題にぴったりです.
誰もが持っているパンツの主な機能は恥をかくことですが、冬になると風を防いで寒さを防ぐことができません.どうすればいいですか?私たちが考えている方法の一つは、パンツを改造して、もっと厚くしてもっと長くすることです.そうすれば、耻ずかしい機能だけでなく、保温も提供できますが、問題があります.このパンツは私たちにパンツに改造された後、耻ずかしい機能もありますが、本質的には本当のパンツではありません.そこで聡明な人々はズボンを発明して、パンツに影響を与えない前提の下で、直接パンツをパンツの外にカバーして、このようにパンツはやはりパンツで、パンツがあってから赤ちゃんは二度と寒くありません.装飾器は私たちがここで言ったズボンのように、パンツの作用に影響を与えない前提の下で、私たちの体に保温の効果を提供しました.
装飾器について話す前に、Pythonの関数はJava、C++とは異なり、Pythonの関数は通常の変数のようにパラメータとして別の関数に渡すことができます.例えば、
正式に私たちのテーマに戻ります.アクセラレータは本質的にPython関数またはクラスであり、他の関数またはクラスがコード変更を必要とせずに追加機能を追加することができ、アクセラレータの戻り値も関数/クラスオブジェクトである.ログの挿入、パフォーマンステスト、トランザクション、キャッシュ、パーミッションチェックなどのシーンでは、面と向かって必要なシーンによく使用されます.デザイナはこのような問題を解決するための絶好の設計です.装飾器があれば、関数機能自体に関係のない同じコードを装飾器に大量に抽出し、再利用を続けることができます.要約すると、装飾器の役割は、既存のオブジェクトに追加の機能を追加することです.
まず簡単な例を見てみましょう.実際のコードはこれよりずっと複雑かもしれませんが、
関数の実行ログを記録し、コードにログコードを追加する新しいニーズがあります.
関数bar()やbar 2()にも似たような需要があれば、どうしますか?bar関数にloggingをもう一つ書きますか?これにより、同じコードが大量に発生します.コードの重複を減らすために、ログを専門に処理し、ログを処理してから本当のビジネスコードを実行する新しい関数を再定義することができます.
論理的には問題ありませんが、機能は実現されていますが、私たちが呼び出すときは本当のビジネスロジックfoo関数を呼び出すのではなく、use_に変更しました.logging関数、これは元のコード構造を破壊して、今私達は毎回元のfoo関数をパラメータとしてuse_に伝達しなければなりませんlogging関数は、もっと良い方法がありますか?もちろんあります.答えは装飾器です.
たんじゅんかざり
use_loggingは装飾器で、普通の関数で、本当のビジネスロジックを実行する関数funcを包み、fooがuseされているように見えます.ロゴは同じように飾られていますuse_loggingは関数を返します.この関数の名前はwrapperです.この例では,関数の進入と終了を横断面と呼び,このプログラミング方式を切断面向けプログラミングと呼ぶ.
@文法糖
Pythonにしばらく触れていたら、@記号に慣れていないに違いありません.間違いありません.@記号は装飾器の文法糖で、関数が定義し始めたところに置いてあります.そうすれば、最後のステップの再付与操作を省略することができます.
以上のように@があればfoo=use_を省くことができますlogging(foo)という文は、foo()を直接呼び出すと所望の結果が得られます.見ましたか.foo()関数は変更する必要はありません.定義された場所に装飾器を付けるだけです.呼び出すときは以前と同じです.他の類似関数があれば、関数を繰り返し変更したり、新しいパッケージを追加したりすることなく、装飾器を呼び出して関数を修飾し続けることができます.これにより,プログラムの再利用性が向上し,プログラムの可読性が向上した.
装飾器がPythonでこのように便利なのは、Pythonの関数が通常のオブジェクトのようにパラメータとして他の関数に伝達できるためであり、他の変数に値を付与することができ、戻り値として、別の関数内に定義することができるからである.
中級知識.
*args、**kwargs
もし私のビジネスロジック関数fooにパラメータが必要ならどうしますか?例:
wrapper関数を定義するときにパラメータを指定できます.
これによりfoo関数で定義されたパラメータをwrapper関数で定義できます.このとき、foo関数が2つのパラメータを受信したら?3つのパラメータは?さらに、私は多くのことを伝えるかもしれません.装飾器がfooにどれだけのパラメータがあるか分からない場合は、*argsで代用できます.
これによりfooがどれだけのパラメータを定義してもfuncに完全に渡すことができます.これでfooのビジネスロジックに影響しません.foo関数にキーワードパラメータが定義されている場合は?例:
この場合、wrapper関数をキーワード関数として指定できます.
パラメータ付き装飾器
装飾器には、パラメータ付き装飾器などのより大きな柔軟性があり、上記の装飾器呼び出しでは、この装飾器が唯一のパラメータを受信するのは、ビジネスを実行する関数fooである.装飾器の構文では、呼び出し時に@decorator(a)などの他のパラメータを指定できます.これにより、装飾器の作成と使用により柔軟性が向上します.たとえば、ビジネス関数によって必要なログ・レベルが異なるため、デザイナでログのレベルを指定できます.
上のuse_loggingはパラメータ付き装飾器です.実際には、既存の装飾器の関数をカプセル化し、装飾器を返します.パラメータを含む閉パケットとして理解できる.@use_を使用するとlogging(level=「warn」)が呼び出されると、Pythonはこのレイヤのパッケージを発見し、パラメータを装飾器の環境に渡すことができます.
@use_logging(level="warn")は@decoratorに等しい
高次の知識.
クラス装飾器
そう、装飾器は関数だけでなく、クラスであってもよく、関数装飾器に比べて、クラス装飾器は柔軟性が大きく、高凝集、パッケージ性などの利点がある.クラスアクセラレータを使用するには、主にクラスに依存します.call__メソッドは、@形式でアクセラレータを関数にアタッチすると呼び出されます.
デザイナを使用してコードを極めて多重化したが、元の関数のメタ情報がなくなったという欠点がある.例えば、関数のdocstring、name、パラメータリスト、まず例を見る.
関数fはwith_loggingが取って代わりました.もちろんdocstring、_name__になりましたlogging関数の情報です.私たちにはfunctoolsがあるwraps、wraps自体も装飾器であり、元の関数のメタ情報を装飾器の中のfunc関数にコピーすることができ、装飾器の中のfunc関数にも元の関数fooと同じメタ情報がある.
デコレーションシーケンス
1つの関数では、次のような複数の装飾を同時に定義することもできます.
その実行順序は中から外へ、最初に最下層の装飾器を呼び出し、最後に最外層の装飾器を呼び出します.これは
初級知識.
Pythonの装飾器について話す前に、まず例を挙げたいと思います.少し汚れていますが、装飾器という話題にぴったりです.
誰もが持っているパンツの主な機能は恥をかくことですが、冬になると風を防いで寒さを防ぐことができません.どうすればいいですか?私たちが考えている方法の一つは、パンツを改造して、もっと厚くしてもっと長くすることです.そうすれば、耻ずかしい機能だけでなく、保温も提供できますが、問題があります.このパンツは私たちにパンツに改造された後、耻ずかしい機能もありますが、本質的には本当のパンツではありません.そこで聡明な人々はズボンを発明して、パンツに影響を与えない前提の下で、直接パンツをパンツの外にカバーして、このようにパンツはやはりパンツで、パンツがあってから赤ちゃんは二度と寒くありません.装飾器は私たちがここで言ったズボンのように、パンツの作用に影響を与えない前提の下で、私たちの体に保温の効果を提供しました.
装飾器について話す前に、Pythonの関数はJava、C++とは異なり、Pythonの関数は通常の変数のようにパラメータとして別の関数に渡すことができます.例えば、
def foo():
print("foo")
def bar(func):
func()
bar(foo)
正式に私たちのテーマに戻ります.アクセラレータは本質的にPython関数またはクラスであり、他の関数またはクラスがコード変更を必要とせずに追加機能を追加することができ、アクセラレータの戻り値も関数/クラスオブジェクトである.ログの挿入、パフォーマンステスト、トランザクション、キャッシュ、パーミッションチェックなどのシーンでは、面と向かって必要なシーンによく使用されます.デザイナはこのような問題を解決するための絶好の設計です.装飾器があれば、関数機能自体に関係のない同じコードを装飾器に大量に抽出し、再利用を続けることができます.要約すると、装飾器の役割は、既存のオブジェクトに追加の機能を追加することです.
まず簡単な例を見てみましょう.実際のコードはこれよりずっと複雑かもしれませんが、
def foo():
print("i am foo")
関数の実行ログを記録し、コードにログコードを追加する新しいニーズがあります.
def foo():
print("i am foo")
logging.info("foo is running")
関数bar()やbar 2()にも似たような需要があれば、どうしますか?bar関数にloggingをもう一つ書きますか?これにより、同じコードが大量に発生します.コードの重複を減らすために、ログを専門に処理し、ログを処理してから本当のビジネスコードを実行する新しい関数を再定義することができます.
def use_logging(func):
logging.warn("%s is running"% func.__name__)
func()
def foo():
print("i am foo")
use_logging(foo)
論理的には問題ありませんが、機能は実現されていますが、私たちが呼び出すときは本当のビジネスロジックfoo関数を呼び出すのではなく、use_に変更しました.logging関数、これは元のコード構造を破壊して、今私達は毎回元のfoo関数をパラメータとしてuse_に伝達しなければなりませんlogging関数は、もっと良い方法がありますか?もちろんあります.答えは装飾器です.
たんじゅんかざり
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func() # foo , func() foo()
return wraper
def foo():
print("i am foo")
foo = use_logging(foo) # use_logging(foo) wrapper, foo = wrapper
foo() # foo() wrapper()
use_loggingは装飾器で、普通の関数で、本当のビジネスロジックを実行する関数funcを包み、fooがuseされているように見えます.ロゴは同じように飾られていますuse_loggingは関数を返します.この関数の名前はwrapperです.この例では,関数の進入と終了を横断面と呼び,このプログラミング方式を切断面向けプログラミングと呼ぶ.
@文法糖
Pythonにしばらく触れていたら、@記号に慣れていないに違いありません.間違いありません.@記号は装飾器の文法糖で、関数が定義し始めたところに置いてあります.そうすれば、最後のステップの再付与操作を省略することができます.
def use_logging(func):
def wrapper():
logging.warn("%s is running", % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo()
以上のように@があればfoo=use_を省くことができますlogging(foo)という文は、foo()を直接呼び出すと所望の結果が得られます.見ましたか.foo()関数は変更する必要はありません.定義された場所に装飾器を付けるだけです.呼び出すときは以前と同じです.他の類似関数があれば、関数を繰り返し変更したり、新しいパッケージを追加したりすることなく、装飾器を呼び出して関数を修飾し続けることができます.これにより,プログラムの再利用性が向上し,プログラムの可読性が向上した.
装飾器がPythonでこのように便利なのは、Pythonの関数が通常のオブジェクトのようにパラメータとして他の関数に伝達できるためであり、他の変数に値を付与することができ、戻り値として、別の関数内に定義することができるからである.
中級知識.
*args、**kwargs
もし私のビジネスロジック関数fooにパラメータが必要ならどうしますか?例:
def foo(name):
print("i am %s" % name)
wrapper関数を定義するときにパラメータを指定できます.
def wrapper(name):
logging.warn("%s is running" % func.__name__)
return func(name)
return wrapper
これによりfoo関数で定義されたパラメータをwrapper関数で定義できます.このとき、foo関数が2つのパラメータを受信したら?3つのパラメータは?さらに、私は多くのことを伝えるかもしれません.装飾器がfooにどれだけのパラメータがあるか分からない場合は、*argsで代用できます.
def wrapper(*args):
loggin.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
これによりfooがどれだけのパラメータを定義してもfuncに完全に渡すことができます.これでfooのビジネスロジックに影響しません.foo関数にキーワードパラメータが定義されている場合は?例:
def foo(name,age=None,height=None):
print("i am %s,age %s,height %s" % (name,age,height))
この場合、wrapper関数をキーワード関数として指定できます.
def wrapper(*args, **kwargs):
# args ,kwargs
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
パラメータ付き装飾器
装飾器には、パラメータ付き装飾器などのより大きな柔軟性があり、上記の装飾器呼び出しでは、この装飾器が唯一のパラメータを受信するのは、ビジネスを実行する関数fooである.装飾器の構文では、呼び出し時に@decorator(a)などの他のパラメータを指定できます.これにより、装飾器の作成と使用により柔軟性が向上します.たとえば、ビジネス関数によって必要なログ・レベルが異なるため、デザイナでログのレベルを指定できます.
def use_logging(level):
def decorator(func):
def wrapper(*args,**kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
elif level == "info":
logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name="foo"):
print("i am %s" % name)
foo()
上のuse_loggingはパラメータ付き装飾器です.実際には、既存の装飾器の関数をカプセル化し、装飾器を返します.パラメータを含む閉パケットとして理解できる.@use_を使用するとlogging(level=「warn」)が呼び出されると、Pythonはこのレイヤのパッケージを発見し、パラメータを装飾器の環境に渡すことができます.
@use_logging(level="warn")は@decoratorに等しい
高次の知識.
クラス装飾器
そう、装飾器は関数だけでなく、クラスであってもよく、関数装飾器に比べて、クラス装飾器は柔軟性が大きく、高凝集、パッケージ性などの利点がある.クラスアクセラレータを使用するには、主にクラスに依存します.call__メソッドは、@形式でアクセラレータを関数にアタッチすると呼び出されます.
class Foo(object):
def __init__(self,func):
self._func = func
def __call__(self):
print("class decorator running")
self.func()
print("class decorator running")
@Foo
def bar():
print("bar")
bar()
デザイナを使用してコードを極めて多重化したが、元の関数のメタ情報がなくなったという欠点がある.例えば、関数のdocstring、name、パラメータリスト、まず例を見る.
#
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ # 'with_logging'
print func.__doc__ # None
return func(*args, **kwargs)
return with_logging
#
@logged
def f(x):
"""does some math"""
return x + x * x
logged(f)
関数fはwith_loggingが取って代わりました.もちろんdocstring、_name__になりましたlogging関数の情報です.私たちにはfunctoolsがあるwraps、wraps自体も装飾器であり、元の関数のメタ情報を装飾器の中のfunc関数にコピーすることができ、装飾器の中のfunc関数にも元の関数fooと同じメタ情報がある.
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 'f'
print func.__doc__ # 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
デコレーションシーケンス
1つの関数では、次のような複数の装飾を同時に定義することもできます.
@a
@b
@c
def f():
pass
その実行順序は中から外へ、最初に最下層の装飾器を呼び出し、最後に最外層の装飾器を呼び出します.これは
f = a(b(c(f)))