python関数プログラミング-アクセサリーdecorator

10943 ワード

関数はオブジェクトであり、変数に値を割り当てることができます.変数によって関数を呼び出すこともできます.
>>> def now():
...     print('2017-12-28')
...
>>> l = now
>>> l()
2017-12-28

関数を使用する_name_関数の名前を取得できます.
>>> now.__name__
'now'
>>> l.__name__
'now'

関数now()を呼び出す前後にログを自動的に印刷するが、now()関数の定義を変更することは許されない場合、コードの実行中に機能を動的に追加する方法を「装飾器」Decoratorと呼ぶ.
たとえば、ログを印刷できるdecoratorを定義します.
>>> def log(func):
...     def wrapper(*args,**kw):
...         print('call %s():' % func.__name__)
...         return func(*args,**kw)
...     return wrapper
...

log関数を観察すると,本質的にはこれが関数を返す高次関数であることが分かった.logはdecoratorとして、パラメータとして関数を受信し、氷ご飯と関数を受信します.pythonの@構文を使用して、decoratorを関数の定義の場所に配置します.
>>> @log
... def now():
...     print('2017-12-28')
...
>>> now()
call now():
2017-12-28

now()関数を呼び出すと、now関数だけでなく、その前にログが印刷されます.
実は,@logをnow()関数の定義の前に置くと,実行に相当する.
now = log(now)
logはdecoratorで、1つの関数を返して、返したこの関数の名前はwrapperで、元のnow()関数はまだ存在して、この時now変数はこの返し関数wrapperを指しました.now()を呼び出すと、新しい関数wrapper関数が実行されます.wrapper関数のパラメータは(*args,**kw)なので、wrapper()関数は任意のパラメータを受け入れることができます!wrapper関数の内部では、まずログを印刷し、元の関数を呼び出します.
分割線------------------------------------
decorator自体がパラメータを入力する必要がある場合は、decoratorを返す高次関数を記述する必要があります.たとえば、logのテキスト、定義、使用方法、および結果をカスタマイズします.
>>> def log(text):
...     def decorator(func):
...         def wrapper(*args,**kw):
...             print('%s %s():' %(text,func.__name__))
...             return func(*args,**kw)
...         return wrapper
...     return decorator
...
>>> @log('  ')
... def now():
...     print('2017-12-28')
...
>>> now()
   now():
2017-12-28

前の例では2層のdefネストが含まれており、後の例では3層のdefネストが含まれている.実際には、3つのネストの効果は似ています.
now=log('実行')(now)
解析:まずlog('実行')を実行し、decorator関数を返し、戻り関数を呼び出します.パラメータはnow関数で、最終的な戻り値はwrapper関数です.
ただし、次の文を実行してテストします.
>>> now.__name__
'wrapper'

decorator装飾後の関数はname__属性はnowからwrapperに変わりました.これは、返されるその関数wrapper関数の名前がwrapperであるため、元の関数の__をname__プロパティはwrapper関数にコピーされます.そうしないと、他の関数署名に依存するコードの実行がエラーになります.
実際にはwrapperを書く必要はありません.name__ = func.__name__このようなコードはpythonにfunctoolsを内蔵しています.wrapsはそのためです.最も
最後のステップでは、完全なdecoratorの書き方は以下の通りです.
>>> import functools
>>> def log(func):
...     @functools.wraps(func)
...     def wrapper(*args,**kw):
...         print('   %s()' %func.__name__)
...         return func(*args,**kw)
...     return wrapper
...
>>> @log
... def now():
...     print('  ')
...
>>> now

>>> now()
   now()
  

パラメータ付きdecoratorの場合:
>>> import functools
>>> def log(text):
...     def decorator(func):
...         @functools.wraps(func)
...         def wrapper(*args,**kw):
...             print('%s %s()' %(text,func.__name__))
...             return func(*args,**kw)
...         return wrapper
...     return decorator
...
>>> @log('ABC')
... def now():
...     print('      ')
...
>>> now()
ABC now()
      

例:任意の関数に作用し、その関数の実行時間を印刷するdecoratorを設計します.
>>> import time,functools
>>> def log(func):
...     @functools.wraps(func)
...     def wrapper(*args,**kw):
...         t1 = time.time()
...         r = func(*args,**kw)
...         print('%s excute in %s ms'%(func.__name__,1000*(time.time()-t1)))
...         return r
...     return wrapper
...
>>> @log
... def fast(x,y):
...     return x+y
...
>>> @log
... def slow(x,y,z):
...     time.sleep(0.1234)
...     return x*y*z
...
>>> @log
... def fast(x,y):
...     time.sleep(0.0012)
...     return x+y
...
>>> fast(3,5)
fast excute in 2.0973682403564453 ms
8
>>> slow(4,5,6)
slow excute in 124.2520809173584 ms
120