対象読者は、高階関数やクロージャーに既に慣れているプログラマー(C#など)ですが、関数内の注釈は「メタ情報」であり、それ自体がリフレクションでのみ現れるという事実に慣れています。 このようなプログラマの目をすぐに引くPythonの機能は、関数を宣言する前にデコレータが存在することで、この関数の動作を変更できることです。
![](https://habrastorage.org/storage2/7ab/96e/ebb/7ab96eebbd0f8788d92860c9c04a57b1.png)
どのように機能しますか? トリッキーなことは何もありません。デコレータは、関数を修飾するための引数を取り、「修正された」ものを返す単なる関数です。
def timed(fn): def decorated(*x): start = time() result = fn(*x) print "Executing %s took %d ms" % (fn.__name__, (time()-start)*1000) return result return decorated @timed def cpuload(): load = psutil.cpu_percent() print "cpuload() returns %d" % load return load print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload()
( 完全なソース )
宣言cpuload .__ name __ ==デコレーション cpuload()は16を返します cpuloadの実行には105ミリ秒かかりました CPU負荷は16%です
@timed def cpuload(): ...
は
def cpuload(): ...; cpuload=timed(cpuload)
展開され
def cpuload(): ...; cpuload=timed(cpuload)
def cpuload(): ...; cpuload=timed(cpuload)
であるため、結果として、グローバル名
cpuload
、
timed
内の
decorated
関数に関連付けられ、
fn
変数を介して元の
cpuload
関数に閉じられます。 その結果、
cpuload.__name__==decorated
値が関数を取り、関数を返す関数である式は、デコレーターとして使用できます。 したがって、「パラメーター付きのデコレーター」(実際には、デコレーターファクトリー)を作成することができます。
def repeat(times): """ times , """ def decorator(fn): def decorated2(*x): total = 0 for i in range(times): total += fn(*x) return total / times return decorated2 return decorator @repeat(5) def cpuload(): """ cpuload """ print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload()
( 完全なソース )
cpuload .__ name __ == decorated2 cpuload()は7を返します cpuload()は16を返します cpuload()は0を返します cpuload()は0を返します cpuload()は33を返します CPU負荷は11%です
repeat(5)
式の値は、
times=5
閉じられる
decorator
関数です。 この値はデコレーターとして使用されます。 実際には
def cpuload(): ...; cpuload=repeat(5)(cpuload)
def cpuload(): ...; cpuload=repeat(5)(cpuload)
1つの関数で複数のデコレータを組み合わせると、自然な順序で右から左に適用されます。 前の2つの例を
@timed @repeat(5) def cpuload():
に結合すると、-出力が得られます
そして、デコレータの順序を変更すると-cpuload .__ name __ ==デコレーション cpuload()は28を返します cpuload()は16を返します cpuload()は0を返します cpuload()は0を返します cpuload()は0を返します decor2の実行には503ミリ秒かかりました CPU負荷は9%です
@repeat(5) @timed def cpuload():
-取得します
最初のケースでは、広告はcpuload .__ name __ == decorated2 cpuload()は16を返します cpuloadの実行には100ミリ秒かかりました cpuload()は14を返します cpuloadの実行には109ミリ秒かかりました cpuload()は0を返します cpuloadの実行には101ミリ秒かかりました cpuload()は0を返します cpuloadの実行には100ミリ秒かかりました cpuload()は0を返します cpuloadの実行には99ミリ秒かかりました CPU負荷は6%です
cpuload=timed(repeat(5)(cpuload))
に展開され、2番目のケースでは
cpuload=repeat(5)(timed(cpuload))
。 印刷された関数名に注意してください。どちらの場合でも、呼び出しのチェーンを追跡できます。
パラメトリックデコレーションの制限ケースは、 デコレータをパラメータとして取るデコレータです 。
def toggle(decorator): """ "" "" """ def new_decorator(fn): decorated = decorator(fn) def new_decorated(*x): if decorator.enabled: return decorated(*x) else: return fn(*x) return new_decorated decorator.enabled = True return new_decorator @toggle(timed) def cpuload(): """ cpuload """ print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload() timed.enabled = False print "CPU load is %d%%" % cpuload()
( 完全なソース )
デコレータの接続/切断を制御する値は、装飾された関数のcpuload .__ name __ == new_decorated cpuload()は28を返します cpuloadの実行には101ミリ秒かかりました CPU負荷は28%です cpuload()は0を返します CPU負荷は0%です
enabled
属性に保存されます。Pythonでは、任意の関数に任意の属性を「固定」できます。
結果の
toggle
関数は、 デコレータのデコレータとしても使用でき
toggle
。
@toggle def timed(fn): """ timed """ @toggle def repeat(times): """ repeat """ @timed @repeat(5) def cpuload(): """ cpuload """ print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload() timed.enabled = False print "CPU load is %d%%" % cpuload()
( 完全なソース )
ええと...いや、うまくいきませんでした! しかし、なぜですか?cpuload .__ name __ == new_decorated cpuload()は28を返します cpuload()は0を返します cpuload()は0を返します cpuload()は0を返します cpuload()は0を返します decor2の実行には501ミリ秒かかりました CPU負荷は5%です cpuload()は0を返します cpuload()は16を返します cpuload()は14を返します cpuload()は16を返します cpuload()は0を返します decor2の実行には500ミリ秒かかりました CPU負荷は9%です
なぜ
cpuload
2回
cpuload
呼び出されても、
timed
デコレータがオフにならないのですか?
timed
のグローバル名は、装飾されたデコレータに関連付けられていることを思い出してください。
new_decorated
関数を使用。 これは、
timed.enabled = False
の行が実際に
new_decorated
関数の属性を変更することを意味します-両方のデコレーターの共通の「ラッパー」です。
if decorator.enabled:
代わりに
new_decorated
内で
new_decorated
if decorator.enabled:
か
if new_decorator.enabled:
を確認すること
if new_decorator.enabled:
が、
timed.enabled = False
の行は両方のデコレーターを一度に無効にします。
このバグを修正しましょう。「内部」デコレータで
enabled
属性を使用するために、以前と同様に、
new_decorated
関数にいくつかのメソッドを
new_decorated
ます。
def toggle(decorator): """ "" "" """ def new_decorator(fn): decorated = decorator(fn) def new_decorated(*x): # if decorator.enabled: return decorated(*x) else: return fn(*x) return new_decorated def enable(): decorator.enabled = True def disable(): decorator.enabled = False new_decorator.enable = enable new_decorator.disable = disable enable() return new_decorator print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload() timed.disable() print "CPU load is %d%%" % cpuload()
( 完全なソース )
希望する結果が達成されます-切断された
timed
ますが、
repeat
続けます:
これはPythonの最も魅力的な機能の1つです。関数だけでなく、任意の関数メソッドにも属性を追加できます。 関数は関数に置かれ、関数を駆動します。cpuload .__ name __ == new_decorated cpuload()は14を返します cpuload()は16を返します cpuload()は0を返します cpuload()は0を返します cpuload()は0を返します decor2の実行には503ミリ秒かかりましたCPU負荷は6%です cpuload()は0を返します cpuload()は0を返します cpuload()は7を返します cpuload()は0を返します cpuload()は0を返します CPU負荷は1%です