7

dict関数呼び出しの結果を関数属性としてキャッシュする、単純な小さなデコレータがあります。

from decorator import decorator
def _dynamic_programming(f, *args, **kwargs):
    try:
        f.cache[args]
    except KeyError:
        f.cache[args] = f(*args, **kwargs)
    return f.cache[args]

def dynamic_programming(f):
    f.cache = {}
    return decorator(_dynamic_programming, f)

キャッシュを空にする可能性を追加したいと思います。したがって、dynamic_programming()関数を次のように変更します。

def dynamic_programming(f):
    f.cache = {}
    def clear():
        f.cache = {}
    f.clear = clear
    return decorator(_dynamic_programming, f)

ここで、この小さなものを使用してフィボナッチ数関数を実装するとします。

@dynamic_programming
def fib(n):
    if n <= 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

>>> fib(4)
5
>>> fib.cache
{(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5}

しかし、キャッシュをクリアすると、奇妙なことが起こります。

>>> fib.clear()
>>> fib.cache
{(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5}

または (新しい Python カーネルが実行されている状態で) 逆の方法で実行します。

>>> fib.clear()
>>> fib(4)
5
>>> fib.cache
{}

clear()キャッシュに最初にアクセスした後、キャッシュが何らかの形で「到達可能」でないのはなぜclear()ですか。

(ちなみに、キャッシュを正しくクリアする解決策を知っています。f.cache.clear()割り当ての代わりに呼び出しを行うと、期待どおりに機能します。割り当ての解決策が失敗する理由{}に興味があるだけです。)

4

2 に答える 2

7

問題はdecoratorモジュールにあります。printデコレーターにいくつかのステートメントを追加すると、次のようになります。

from decorator import decorator
def _dynamic_programming(f, *args, **kwargs):
    print "Inside decorator", id(f.cache)
    try:
        f.cache[args]
    except KeyError:
        f.cache[args] = f(*args, **kwargs)
    return f.cache[args]

def dynamic_programming(f):
    f.cache = {}
    print "Original cache", id(f.cache)
    def clear():
        f.cache = {}
        print "New cache", id(f.cache)
    f.clear = clear
    return decorator(_dynamic_programming, f)

@dynamic_programming
def fib(n):
    if n <= 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

print fib(4)
print id(fib.cache)
fib.clear()
print id(fib.cache)
print fib(10)
print id(fib.cache)

出力します(重複行はスキップされます):

Original cache 139877501744024
Inside decorator 139877501744024
5
139877501744024
New cache 139877501802208
139877501744024
Inside decorator 139877501802208
89
139877501744024

ご覧のとおりcache、 clear 関数に応じてデコレータの内部が変化します。ただし、cacheアクセス元__main__は変わりません。デコレーターの外側と内側を印刷するcacheと、より鮮明な画像が得られます (ここでも、重複はスキップされます)。

Inside decorator {}
Inside decorator {(1,): 1}
Inside decorator {(2,): 2, (0,): 1, (1,): 1}
Inside decorator {(2,): 2, (0,): 1, (3,): 3, (1,): 1}
5
Outside {(2,): 2, (0,): 1, (3,): 3, (1,): 1, (4,): 5}
Inside decorator {}
Inside decorator {(1,): 1}
Inside decorator {(2,): 2, (0,): 1, (1,): 1}
Inside decorator {(2,): 2, (0,): 1, (3,): 3, (1,): 1}
Inside decorator {(2,): 2, (0,): 1, (3,): 3, (1,): 1, (4,): 5}
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5, (5,): 8}
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5, (5,): 8, (6,): 13}
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (3,): 3, (4,): 5, (5,): 8, (6,): 13, (7,): 21}
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (8,): 34, (3,): 3, (4,): 5, (5,): 8, (6,): 13, (7,): 21}
Inside decorator {(0,): 1, (1,): 1, (2,): 2, (8,): 34, (3,): 3, (9,): 55, (4,): 5, (5,): 8, (6,): 13, (7,): 21}
89
Outside {(2,): 2, (0,): 1, (3,): 3, (1,): 1, (4,): 5}

ご覧のとおり、内部の変更は外部には反映されていません。問題はdecoratorモジュール内に次の行があることです (デコレータを作成するために使用されたクラス内):

self.dict = func.__dict__.copy()

そして後で

func.__dict__ = getattr(self, 'dict', {})

基本的に、外側と内側__dict__は異なります。__dict__この意味は:

  • __dict__デコレータによってコピーされます (参照されません)。
  • cache変化するとき、それ__dict__は外側ではなく内側を変える__dict__
  • したがって、によってcache使用されるはクリアされますが、デコレーターがまだ古いものを指しているため_dynamic_programming、外側からはそれを見ることができません(上でわかるように、内側は更新されますが、外側は同じままです)。__dict__cachecachecache

つまり、要約すると、それはdecoratorモジュールの問題です。

于 2014-10-29T17:14:11.593 に答える