[転載]Python装飾器の最も率直な理解

21984 ワード

https://www.runoob.com/w3cnote/python-func-decorators.html一番下に引いてコメントを見て以下は上記のリンクから写します
初級知識.
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)))