ログを印刷してこの関数の呼び出しステータスを出力するなど、関数の実行の前後に他のコードを実行する場合はどうなりますか?
#! /usr/bin/env python
# coding=utf-8
def logger(fn): #パラメータとして機能します。fnは任意のパラメータにすることができます
def wrap(*args,**kwargs): #可変パラメーターargsおよびkwargs
print('call {}'.format(fn.__name__))
ret =fn(*args,**kwargs) #関数呼び出し中のパラメーターの分解
print('{} called'.format(fn.__name__))return ret #関数の戻り値を返します
return wrap
def add(x, y):return x + y
logger_add =logger(add)print(logger_add.__name__)print(logger_add)
ret =logger_add(3,5)print(ret)
# 出力結果:
wrap
< function logger.<locals>.wrap at 0x7fba35f4fe18>
call add
add called
8
この効果を実現するには、次の方法を使用することもできます
@ logger
def add(x, y):return x + y ret =add(3,5)print(ret)
# 出力結果:
call add
add called
8
これはPythonデコレータの簡単な使用法です
デコレータは、ソフトウェアの設計パターンに使用される名前です。デコレータは、サブクラスを直接使用したり、装飾された関数のソースコードを変更したりすることなく、関数、メソッド、またはクラスの関数を動的に変更できます。 Pythonデコレータは、Python構文に対する特別な変更であり、関数、メソッド、およびクラスをより便利に変更できるようにします。
次のようにコードを書くとき:
@ logger
def add(x, y):...
これは、次の手順を個別に実行することと同じです。
def add(x, y):...
logger_add =logger(add)
デコレータ内のコードは通常、 * args
と ** kwargs
を使用して任意のパラメータを受け入れる新しい関数を作成します。上記のコードのwrap()関数は次のようになります。この関数内で、元の入力関数(デコレータの入力パラメータであるラップされた関数)を呼び出して、その結果を返す必要があります。ただし、上記のコードで関数呼び出しを出力したり、タイミング処理を追加したりするなど、追加したいコードを追加することもできます。この新しく作成されたラップ関数は、元の関数を置き換えて、デコレータの結果として返されます。
したがって、** Pythonでは、デコレータのパラメータは関数であり、戻り値は関数の関数です**。
関数の実行時間を計算するためのデコレータを作成します
import time
def timethis(fn):
def wrap(*args,**kwargs):
start = time.time()
ret =fn(*args,**kwargs)
end = time.time()print(fn.__name__, end - start)return ret
return wrap
追加機能の時間を計りたい場合:
@ timethis
def add(x, y):return x + y
ret =add(3,5)print(ret)
# 出力結果
add 1.9073486328125e-068
スリープ機能の時間を計りたい場合:
@ timethis
def sleep(x):
time.sleep(x)sleep(3)
# 出力結果
sleep 3.003262519836426
たとえば、デコレータの名前、デコレータのドキュメントなどです。 dir関数を使用して、関数のすべてのメタ情報を一覧表示できます。 dir(sleep)
、出力は次のとおりです。
['__ annotations__','__call__','__class__','__closure__','__code__','__defaults__','__delattr__','__dict__','__dir__','__doc__','__eq__','__format__','__ge__','__get__','__getattribute__','__globals__','__gt__','__hash__','__init__','__kwdefaults__','__le__','__lt__','__module__','__name__','__ne__','__new__','__qualname__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__']
メタ情報がたくさんあることがわかります。使用する2つの属性は、 __name__
と __doc__
です。
また、 __doc__
属性は関数のドキュメント情報であり、ヘルプ関数を介して表示できます。
デコレータ1のアプリケーションを書き直します。タイミングプロセスのスリープ機能は次のとおりです。
@ timeit
def sleep(x):'''This function is sleep.'''
time.sleep(x)sleep(3)print(sleep.__name__)print(sleep.__doc__)
上記のコードの出力は次のとおりです。
3.0032713413238525
wrap
None
スリープ関数の __name__
はスリープではなくラップであり、 __doc__
属性はスリープ関数のdocstringではなく空であることがわかります。つまり、デコレータによって装飾された関数のメタ情報が変更されたため、この時点で、プログラムが関数のメタ情報を必要とする場合、問題が発生します。
例として __name__
と __doc__
の2つの属性を取り上げます
import time
def timeit(fn):
def wrap(*args,**kwargs):
start = time.time()
ret =fn(*args,**kwargs)
end = time.time()print(end - start)return ret
wrap.__doc__ = fn.__doc__ #手動割り当て__doc__情報
wrap.__name__ = fn.__name__ #手動割り当て__name__情報
return wrap
@ timeit
def sleep(x):'''This function is sleep.'''
time.sleep(x)if __name__ =="__main__":sleep(3)
# print(dir(sleep))print(sleep.__name__)print(sleep.__doc__)
出力は次のとおりです
3.004547119140625
sleep
This function is sleep.
__name__
と __doc__
の2つの属性が実際に正常に割り当てられていることがわかります。
メタ情報の割り当てのプロセスを関数として次のように書き直すことができます。
import time
def copy_properties(src, dst): #メタ情報の割り当てプロセスを機能コピーに変更する_properties
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
def timeit(fn):
def wrap(*args,**kwargs):
start = time.time()
ret =fn(*args,**kwargs)
end = time.time()print(end - start)return ret
copy_properties(fn, wrap) #コピーを呼び出す_プロパティはメタ情報を変更する機能
return wrap
@ timeit
def sleep(x):'''This function is sleep.'''
time.sleep(x)if __name__ =="__main__":sleep(3)
# print(dir(sleep))print(sleep.__name__)print(sleep.__doc__)
この変更後、問題も解決できます。
copy_propertiesが関数を返すことができるように、copy_properties関数を引き続き変更します。
def copy_properties(src):
def _copy(dst): #ビルトインワン_コピー機能は簡単に返せます
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return _copy
def timeit(fn):
def wrap(*args,**kwargs):
start = time.time()
ret =fn(*args,**kwargs)
end = time.time()print(end - start)return ret
copy_properties(fn)(wrap) #コピーを呼び出す_プロパティ関数
return wrap
同じことが問題になる可能性があります。
_copy関数がデコレータになるようにcopy_properties関数を変更し続ける場合は、dstを渡してdstを返し、次のように変更します。
def copy_properties(src): #最初にdstを修正し、srcを渡します
def _copy(dst): #着信dst
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__
return dst #dstを返す
return _copy #デコレータを返します
def timeit(fn):
@ copy_properties(fn) #パラメータデコレータの使用方法
def wrap(*args,**kwargs):
start = time.time()
ret =fn(*args,**kwargs)
end = time.time()print(end - start)return ret
return wrap
copy_propertiesは、ここにパラメーターを持つデコレーターを返すため、デコレーターの使用方法に従ってラップ関数を直接装飾できます。copy_properties関数を変更するこのプロセスは、関数カレーと呼ばれます。
functoolsライブラリの@wrapsデコレータは、本質的にcopy_properties関数の高度なバージョンであり、より多くの関数メタ情報が含まれています。まず、ラップデコレータのヘルプ情報を確認します。
import functools
help(functools.wraps)
ラップデコレータ関数のプロトタイプは次のとおりです。
wraps(wrapped, assigned=('module','name','qualname','doc','annotations'), updated=('dict',))
したがって、このデコレータはモジュールなどのメタ情報をコピーしますが、すべてのメタ情報をコピーするわけではなく、dictを更新します。
使用例は次のとおりです。
import time
import functools
def timeit(fn):
@ functools.wraps(fn) #ラップデコレータの使用
def wrap(*args,**kwargs):
start = time.time()
ret =fn(*args,**kwargs)
end = time.time()print(end - start)return ret
return wrap
def sleep(x):
time.sleep(x)print(sleep.__name__)print(sleep.__doc__)
上記のtimeitデコレータの場合、実行時間が数秒(1秒など)を超える関数の名前と実行時間を出力する必要がある場合は、入力時間間隔を表すパラメータsをデコレータに渡す必要があります。デフォルトは1です。 。
記述されたデコレータの外側で関数timeitSをラップすることができます。時間間隔sは、この関数のパラメータとして渡され、内部関数に表示されます。その後、この関数は記述されたデコレータを返します。
import time
import functools
def timeitS(s):
def timeit(fn):
@ functools.wraps(fn)
def wrap(*args,**kwargs):
start = time.time()
ret =fn(*args,**kwargs)
end = time.time()if end - start > s:print('call {} takes {}s'.format(fn.__name__, end - start))else:print('call {} takes {}s less than {}'.format(fn.__name__, end - start, s))return ret
return wrap
return timeit
@ timeitS(2)
def sleep(x):
time.sleep(x)sleep(3)sleep(1)
出力は次のとおりです。
call sleep takes 3.001342535018921s
call sleep takes 1.000471830368042s less than 2
したがって、次のようなパラメータを持つデコレータを理解できます。
Recommended Posts