Dive Into Python 3 Appendix.C 「ここからどこへ進むのか」にて、Ariel Ortizによる 関数デコレータ、続関数デコレータ が紹介されていた。
早速見てみると、分かり易くて初学者には良いなと思った。
どうせ、記憶力がない私のこと、何度も忘れて見に行くことを考えると、
一度、勉強がてら、翻訳しておこかなぁ?と思い、
作者のAriel Ortizに翻訳とブログへの掲載・コメント
許可を求めたところ「OK」とのお返事を頂きました。
Ariel Ortizのブログページ ProgrammingBits
英語もPythonもアレな私が翻訳しているので、少しでもオカシイところがあれば、
それは私の責です。原文をあたって下さいませ。突っ込みお願いします。
関数デコレーター
プログラミング言語Pythonは、デコレーターと呼ばれる興味深い
シンクタックス上の機能を備えている。
どうやって、そしてどうしてPythonのでデコレーター
を使うのか?さあ、例題を1個使って説明しよう。
NOTE:ここで例示する、すべてのPythonコードはPython3に基づいている。
ソースコードの全文は以下。
http://programmingbits.pythonblogs.com/gallery/27/function_decorators.py
再帰関数を使って、フィボナッチ数を計算したいとする。
次の関数定義で、うまくいく。(1)
def fib(n): if n in (0, 1): return n else: return fib(n - 1) + fib(n - 2)
これで異なる入力値で関数をテストすることができる。
>>> fib(0)
0
>>> fib(1)
1
>>> fib(4)
3
>>> fib(10)
55
>>> fib(30)
832040
>>> fib(35)
9227465
あなたが、正確にこれらサンプルをPython shellにタイプしたならば
入力値が大きいほど、結果を得るのに時間がかかることに気がついただろう。
高速に動かすために、memoizationと呼ばれるテクニックを使うことができる。
memoizeされた関数は、特定の入力値に該当する結果を、キャッシュに格納する。
呼び出しの後、前回の入力値に対する計算結果として、結果をリターンすると共に、
キャッシュに格納する。すなわち、再び計算するのを避けるのだ。
このことは、特定のパラメータでの関数コールにかかる主要コストは、
次に同じパラメータでよばれた関数に最初にかかるコストを、
肩代わりしていることになる。
fib関数を直接変更するかわりに、再利用可能なmemoizing関数を書くほうがよいだろう。
def memoize(f): cache = {} def helper(x): if x not in cache: cache[x] = f(x) return cache[x] return helper
memoize関数は別のhelper関数をラップしている。
そしてパラメータ f として受け取った関数に追加機能をする。
helper関数は実際にレキシャルクロージャーだ。レキシャルクロージャーとは、関数自身が作成された時の、自身のスコープ内にある変数を記憶している関数オブジェクトである。
このケースでいうと、helperは変数 fとcache(関数オブジェクトと辞書をそれぞれ保持する)を覚えていて最後の行では、memoize関数の呼び出し元にhelper lexicalクロージャーを返している。
以下のコードをタイプしてみると
fibをコールしたときに、すぐにスピードがアップしていることに気がつくだろう。
すごく初期のコール時でさえも、スピードアップを認めることができる。
何故なら、直ぐにfibの内部に再帰呼び出しを積み上げるからだ。
>>> fib = memoize(fib)
>>> fib(50)
12586269025
最初の行はfibをmemoizeでデコレートしていると読むことができる。
何故なら、これはPythonにおける、共通のプログラミングのイディオムのようなもので、
それを単純に利用するために、特別なデコレータシンクタックスがある。
このシンクタックスはJavaのアノテーションにインスパイアされたもので、
デコレートされる関数の定義の前に、@につづけてデコレータ関数の名前を記述する。
別の言い方をすると、このPythonのシンクタックスは
@some_decorator def some_function(): # function body.
以下と同じ意味である。
def some_function(): # function body... some_function = some_decorator(some_function)
ほとんどの場合は
@ノーテーションの方が読みやすくエラーになりにくいと同意すべきだろう。
フィボナッチ、memoization関数の例で、Pythonデコレーターを
利用するために、次のように書き直さなくてはならないだろう。
def memoize(f): # 前出のコード... @memoize def fib(n): # 前出のコード
memoize関数は典型的な関数デコレータが持っているべき一般的な構造をみせてくれる。
一つの関数 f をインプットパラメータとして受け取り、関数 f に追加の責務(出来ること)を追加する
別の関数を戻り値とするのだ。
Pythonデコレータの別の面白い点としては、2つ以上のそれを連結することができるというものだ。
それでは、 デコートされた関数が呼ばれる前と、戻り値を返す前にメッセージを出力するような
トレース・デコレータを加えるためにサンプルを拡張してみよう。
def memoize(f): # 前出のコード... def trace(f): def helper(x): call_str = "{0}({1})".format(f.__name__, x) print("Calling {0} ...".format(call_str)) result = f(x) print("... returning from {0} = {1}".format( call_str, result)) return result return helper @memoize @trace def fib(n): # 前出のコード...
@memoizeと@traceはfibの直前に定義している。
さっそく、fibを実行してみると最初にmemoizeデコレータが呼ばれる。それからtraceデコレータが呼ばれ
最後に、オリジナルのfibコードが呼ばれる。例を確認してみよう。
>>> fib(5)
Calling fib(5) ...
Calling fib(4) ...
Calling fib(3) ...
Calling fib(2) ...
Calling fib(1) ...
... returning from fib(1) = 1
Calling fib(0) ...
... returning from fib(0) = 0
... returning from fib(2) = 1
... returning from fib(3) = 2
... returning from fib(4) = 3
... returning from fib(5) = 5
5
Pythonは関数デコレータとして使用されることを意図した、3種類のビルトイン関数を持っている。
- classmethod: デコレータメソッドが、クラスメソッドであることを指す為に使用される。SmalltalkやRubyのそれに似ている。
- staticmethod: デコレータメソッドが、スタティックメソッドであることを指す為に使用される。C++、C#やJAVAのそれに似ている。
- property: オブジェクトのプロパティを取得、設定、削除するために使用される。
Python3.0は関数をデコレータするだけでなく、クラスをデコレートすることも許可することは、注意しておく価値がある。
うまくいけば、こんどのポストでこれらの特定種類のデコレータについて書くために時間を取るつもりだ。。
Notes
(1) 再帰を用いたフィボナッチ数列の実装はとても効率が悪いのは、知っている。
だけど、それが私の意図だ。
遅さを体感できるよう、私は単純なアルゴリズムがほしかった。
以上です。アホな私にも理解できたような気がする。分かりやすいです。
続・関数デコレータも読も。
6/11 修正
0 件のコメント:
コメントを投稿