毎日Learning

学んだことを共有します

エキスパート Python プログラミング メモ Vol.4

かなり間が空いてしまった。途中、浮気(Slim3本)を読んだりしてたし…。

今日から毎日4ページずつ読んではブログに書くようにしよう。

4ページが無理っぽかったら、3ページになるかもしれない…。

2.3 デコレータ

デコレータはPython 2.4で追加された構文で、関数やメソッドのラッピング(受け取った関数を拡張して返す)処理の見た目をわかりやすくします。
この構文は、クラスメソッドや静的なメソッドを定義する際に、メソッド定義の前の行にすっきりと書けるようにしたい動機から追加されました。
デコレータ導入以前は、次のような構文を使用していました。

>>> class WhatFor(object):
...     def it(cls):
...         print ('work with %s' % cls)
...     it = classmethod(it)                # クラスメソッドにする
...     def uncommon():
...         print ('I could be a global function')
...     uncommon = staticmethod(uncommon)   # 静的メソッドにする

まあこれはこれでありっちゃありやけど、まあ、ださいわなってーことです。

そういや、Javaアノテーションも、こんな感じだったよね。昔。

で、デコレータ構文だと、以下。

>>> class WhatFor(object):
...     @classmethod
...     def it(cls):
...         print ('work with %s' % cls)
...     @staticmethod
...     def uncommon():
...         print ('I could be a global function')
...
>>> this_is = WhatFor()
>>> this_is.it()
work with <class '__main__.WhatFor'>
>>> this_is.uncommon()
I could be a global function

だとさ。これはすっきりしてかっちょえー。ってことで、Pythonコミュニティの多くの開発者がデコレータを使うようになったとのことです。

以降は、デコレータの書き方と複数の例を紹介するんですって。

2.3.1 デコレータの書き方

デコレータは色んな書き方があるけど、以下がよく使われるんだって。

>>> def mydecorator(function):
...     def _mydecorator(*args, **kw):
...         # 実際の関数を呼び出す前に行う処理
...         res = function(*args, **kw)
...         # 呼び出し後に行う処理
...     # 内部で作成したサブ関数を返す
...     return _mydecorator

訳注: この例のような元の関数をラップするデコレータを使用した場合、関数名やdocstringがデコレータで定義したものに置き換わってしまい、
helpだどで表示した場合に、期待した結果を得ることができません。

>>> @mydecorator
... def my_function(arg):
...     """my docstring"""
...     return arg
...
>>> # 関数名が変わってしまった!
>>> my_function.func_name
'_mydecorator'
>>> # docstringも変わってしまった!
>>> my_function.func_doc
>>> help(my_function)
Help on function _mydecorator in module __main__:

_mydecorator(*args, **kw)

なんですと!!

まあ、日頃、あまり書かないんだけども、おっきなプロジェクト(があったとしたら)頻繁に書くことになるんだろう。

元の関数名を保持するには、func_nameとfunc_docを書き換える必要があるんだって。

Python 2.5以降であれば、functoolsモジュールのwrapsデコレータを置き換える関数の前につけるだけで、正しい関数名とdocstringが設定されます。

>>> from functools import wraps
>>> def mydecorator(function):
...     @wraps(function)
...     def _mydecorator(*args, **kw):
...         return function(*args, **kw)
...     return _mydecorator
...
>>> @mydecorator
... def my_function(arg):
...     """my docstring"""
...     return arg
...
>>> # 関数名もdocstringも正しくなった。
>>> my_function.func_name
'my_function'
>>> my_function.func_doc
'my docstring'
>>> help(my_function)
Help on function _mydecorator in module __main__:

my_function(*args, **kw)
    my docstring

とのことでした。