遊ぶ---python-装飾器

21201 ワード

pythonの装飾器は本質的にPython関数であり、他の関数がコードの変動を必要とせずに追加機能を追加することができます.
                。                    。

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 wrapper 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): logging.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 runing') self._func() print ('class decorator ending') @Foo def bar(): print ('bar') bar() functools.wraps

アクセラレータを使用してコードを極めて多重化したが、元の関数のメタ情報がなくなったという欠点がある.例えば、関数の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)))