Pythonのデコレータを理解するときに残したメモ
Pythonのデコレータ(decorator)を理解する 3 - -を1,2,3全て読んで、以前僕がデコレータを理解するために残したメモもこんな感じだったなというのを思い出した。ずっと記事にしようと思いつつ忘れてたんだけどせっかくなので加筆修正しつつ公開してみる。
引数無しのデコレータ
デコレータとは、その名の通り元の関数を装飾するもの。別の言い方をすれば、元の関数そのものを弄ること無く挙動を変更するもの。
まずは単純なデコレータから。
def hello(function): def _hello(*args, **kw): result = function(*args, **kw) return "Hello, {0}".format(result) return _hello @hello def name(arg): """my docstring""" return arg # これは以下と同義。実際デコレータ式が導入される前はこうしていた。 def name(arg): """my docstring""" return arg name = hello(name) # 実行 print name('John Doe')
- デコレータ付ける前:name('John') # 'John'
- デコレータ付けた後:name('John') # 'Hello, John'
元の関数nameは単純に与えられた引数をそのまま返すだけ。そこにデコレータhelloを付けることによって、"Hello, xxxx"のような文字列を返すように挙動が変更される。デコレータ式じゃない方を見てみるとわかるように、関数を返す関数helloの引数として関数nameを渡し、返ってきた関数をnameに再代入する感じ。
ところで、これだと元の関数名がデコレータの内側の関数名で上書きされてしまう。
name.func_name # '_hello' name.func_doc # None
これを解決するためには、functoolモジュールのwrapsを使う。
from functools import wraps def hello(function): @wraps(function) def _hello(*args, **kw): return function(*args, **kw) return _hello @hello def name(arg): """my docstring""" return arg
これで以下のように関数名と関数のdocstringが正しく取得できるようになる
name.func_name # 'name' name.func_doc # 'my docstring'
デコレータ自体に引数を与える
flaskの簡単なview関数
@app.route('/') def hello(): return "Hello World!"
こんな風にデコレータ自体に引数を渡したい場合はどうするか。
この場合、
- デコレート対象の関数の引数を受ける関数
- デコレート対象の関数自身を引数として受け取る関数
- デコレータ自身の引数を受け取る関数
が必要になるので、少なくとも3重にネストした関数になる。
from functools import wraps def hello(friend1, friend2): def _hello(function): @wraps(function) def __hello(*args, **kw): result = function(*args, **kw) return "Hello, {0} & {1}! I'm {2}.".format(friend1, friend2, *args, **kw) return __hello return _hello @hello("Jack", "Mary") def name(arg): return arg # デコレータ式を使わない場合 # 式の左側の変数名はイメージしやすいように_helloとかnameを使ったけれど別になんでもいい。 _hello = hello("Jack", "Mary") name = _hello(name) print name("John") # 'Hello, Jack & Mary! I'm John.'