Python / デコレータのカスタム

前回の投稿からの続きです。

関数内関数定義を使う

前回までは、デコレータの記述に無名関数を使用していました。 Pythonは関数内で関数を定義することができるので、無理に無名関数を使わずに、 定義した関数を返したほうが、可読性が良くなる場合があります。


# 無名関数デコレータ
>>> def dec00(a_func):
...     return (lambda *args,**kwds: a_func(*args,**kwds))
...

# 関数内関数定義デコレータ
>>> def dec01(a_func):
...     def wrapper(*args, **kwds):
...             return a_func(*args, **kwds)
...     return wrapper
...

# どちらも、渡された関数を呼び出しているだけ

関数内関数定義デコレータの返す関数の名前は、慣習的に'wrapper'とされるようです。

functoolsを使う

Pythonは内省的なので、関数の名前を実行時に取得できます。


>>> def hoge():
...     return 'hoge'
...

# '__name__'に名前が入っています
>>> hoge.__name__
'hoge'

デコレータによって、この名前は上書きされてしまいます。


>>> dec00(hoge).__name__ # 無名関数デコレータ
'<lambda>'

>>> dec01(hoge).__name__ # 関数内関数定義デコレータ
'wrapper'

標準ライブラリfunctools.wrapsを使うことで、これを修正できます。


import functools

# 無名関数デコレータ
>>> def dec00(a_func):
...     return functools.wraps(a_func)(lambda *args,**kwds:a_func(*args,**kwds))
...
>>> dec00(hoge).__name__
'hoge'

# 関数内関数定義デコレータ
>>> def dec01(a_func):
...     @functools.wraps(a_func)
...     def wrapper(*args, **kwds):
...             return a_func(*args, **kwds)
...     return wrapper
...
>>> dec01(hoge).__name__
'hoge'

functools.wrapsの引数に元の関数を渡していて、 functools.wraps自体がデコレータとして適用できます。

デコレータのカスタマイズ

functools.wrapsのようにデコレータ自体に引数を渡して振舞を変更したい場合、 デコレータを返す関数として定義することになります。


>>> def dec02(a_name):
...     def decorator(a_func):
...             @functools.wraps(a_func)
...             def wrapper(*args,**kwds):
...                     return a_name + a_func(*args,**kwds)
...             return wrapper
...     return decorator
...
# a_name = 'dec02'としてデコレータを作成し、fugaに適用
>>> @dec02('dec02')
... def fuga(a_msg):
...     return a_msg
... 
>>> fuga('fuga')
'dec02fuga'

"'関数を受け取り関数を返す関数'を返す関数"です。 ……字面を考えると解らなくなりますね。

──続きます。

目録

  1. Python / 不便が便利
  2. Python / 内省的(introspective)とはどういうことか
  3. Python / 第一級関数と無名関数
  4. Python / デコレータは愉快
  5. Python / デコレータと引数
  6. Python / デコレータのカスタム
  7. Python / IndentデコレータとChoiceデコレータ

0 件のコメント:

コメントを投稿