7

私はウェブ上で見つけたこの優れたメモ化用デコレータを使用しています (ここでは、例としてフィボナッチ数列を示しています)。

def memoize(f):
    cache= {}
    def memf(*x):
        if x not in cache:
            cache[x] = f(*x)
        return cache[x]
    return memf

@memoize
def fib(n):
    if n==1 or n==0:
        return 1
    return fib(n-2) + fib(n-1)

print fib(969)

ここで、内部の仕組みをもう少しよく理解したいと思います。デコレータや Python でのパラメータ処理を読んでも答えは見つかりませんでした。

装飾された関数が呼び出されるたびにキャッシュ辞書が再初期化されないのはなぜですか?

*x は、装飾された関数、つまり関数呼び出し fib(969) の 969 に送信されるパラメーターであるとどのように認識されますか?

4

1 に答える 1

9

デコレータは、装飾された関数が最初に定義された直後に一度だけ呼び出されます。したがって、これら 2 つの手法 (@wrap と bar = wrap(bar) を使用) は同じです。

>>> def wrap(f):
...     print 'making arr'
...     arr = []
...     def inner():
...         arr.append(2)
...         print arr
...         f()
...     return inner
...     
>>> @wrap
... def foo():
...     print 'foo was called'
...     
making arr
>>> foo()
[2]
foo was called
>>> foo()
[2, 2]
foo was called
>>> def bar():
...     print 'bar was called'
...     
>>> bar = wrap(bar)
making arr
>>> bar()
[2]
bar was called

どちらの場合も、arr は wrap(f) が呼び出されたときにのみ作成され、wrap は foo と bar が最初に宣言されたときにのみ呼び出されることは明らかです。

装飾された関数に引数を渡す場合については、デコレーターが関数をパラメーターとして取り、その関数の変更されたバージョンを返すことを思い出してください。したがって、デコレーターは通常、変更する関数である 1 つのパラメーターを取ります。これは新しい関数を返します。デコレーターは、任意の数の引数 (*args など) を受け取る関数を定義できます。デコレーターは、デコレートするメソッドのパラメーターが多すぎる関数を返すことさえあります。

>>> def wrap_with_arg(f):
...     def wrap(*args):
...         print 'called with %d arguments' % len(args)
...         f(args)
...     return wrap
...     
>>> @wrap_with_arg
... def baz(arg):
...     print 'called with argument %r' % arg
...     
>>> baz(3)
called with 1 arguments
called with argument 3
>>> baz(3, 4)
called with 2 arguments
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 4, in wrap
  File "<input>", line 3, in baz
TypeError: not all arguments converted during string formatting

最終的に baz はエラーをスローしますが、エラーがスローされる前に引数の数が正しく出力されることに注目してください。

于 2012-04-04T12:30:44.843 に答える