6

Python関数オブジェクトには、func_dict関数の外部から表示され、変更可能な属性ディクショナリがありますが、関数が呼び出されても変更されません。(昨日尋ねた質問への回答からこれを学びました(#1753232):ありがとう!)私はフィボナッチ数の計算をメモしたコード( http://pythonprogramming.jottit.com/functional_programmingfunc_dict )を読んでいて、「なぜメモ化に属性を使用しませんか?」それは機能しました(以下を参照してください;出力はコードの最後にあります)。これは、クラスプロパティを使用できるのと少し似ていますが、オブジェクトの外部に初期化コードがあります(この場合、クラスではなく関数です)。

この属性を使用して、類似した(または類似していない)トリックを実行できるのだろうか?

def fib(n):
    if n in fib.cache:
        print "found fib.cache[%d] = %d: " %(n, fib.cache[n])
        return fib.cache[n]
    else:
        print "fib.cache[%d] = fib(%d) + fib(%d)" % (n, n-1, n-2)
        fib.cache[n] = fib(n-1) + fib(n-2)
        print "modified fib.cache: ", fib.cache
        return fib.cache[n]

fib.cache = {0:0, 1:1}

for x in range(7):
    print "==================>", x
    print fib( x)

"""
==================> 0
found fib.cache[0] = 0: 
0
==================> 1
found fib.cache[1] = 1: 
1
==================> 2
fib.cache[2] = fib(1) + fib(0)
found fib.cache[1] = 1: 
found fib.cache[0] = 0: 
modified fib.cache:  {0: 0, 1: 1, 2: 1}
1
==================> 3
fib.cache[3] = fib(2) + fib(1)
found fib.cache[2] = 1: 
found fib.cache[1] = 1: 
modified fib.cache:  {0: 0, 1: 1, 2: 1, 3: 2}
2
==================> 4
fib.cache[4] = fib(3) + fib(2)
found fib.cache[3] = 2: 
found fib.cache[2] = 1: 
modified fib.cache:  {0: 0, 1: 1, 2: 1, 3: 2, 4: 3}
3
==================> 5
fib.cache[5] = fib(4) + fib(3)
found fib.cache[4] = 3: 
found fib.cache[3] = 2: 
modified fib.cache:  {0: 0, 1: 1, 2: 1, 3: 2, 4: 3, 5: 5}
5
==================> 6
fib.cache[6] = fib(5) + fib(4)
found fib.cache[5] = 5: 
found fib.cache[4] = 3: 
modified fib.cache:  {0: 0, 1: 1, 2: 1, 3: 2, 4: 3, 5: 5, 6: 8}
8
"""
4

2 に答える 2

7

注意してください。このfib.cacheトリックfibは、実行中にアクティブなスコープ内の関連する関数オブジェクトの名前である場合にのみ機能します(たとえば、実行中にデコレーションする場合は、キャッシュの開始値をに割り当てる必要があります。デコレータラッパーであり、decorated関数ではありません。その後さらに装飾されると、問題が発生します)。

これは、標準のメモ化イディオムと比較して少し壊れやすいです。

def fib(n, _memo={0:1, 1:1}):
    if n in _memo:
        return _memo[n]
    else:
        _memo[n] = fib(n-1) + fib(n-2)
        return _memo[n]

または同等のデコレータ。標準のイディオムも高速です(それほどではありませんが)-mem.pyにfib1(.cache-trickのもの、印刷物や装飾なし)とfib2(私のバージョン)の両方の名前を付けます...:

$ python -mtimeit -s'import mem' 'mem.fib1(20)'
1000000 loops, best of 3: 0.754 usec per loop
$ python -mtimeit -s'import mem' 'mem.fib2(20)'
1000000 loops, best of 3: 0.507 usec per loop

したがって、標準イディオムバージョンは時間の約33%を節約しますが、ほとんどすべての呼び出しが実際にメモ化キャッシュ(これらの100万ループの最初のループの後に入力されます)にヒットするときです-fib2の速度の利点は、キャッシュミスの場合は小さくなります。これは、 _memo(ローカル変数)とfib.cache(グローバル名、fib、そしてその属性であるキャッシュ)のアクセス速度が速いためであり、キャッシュアクセスはキャッシュヒットを支配します(他に何もありません;-)が、少し余分なものがありますキャッシュミスの処理(両方の機能で同等)。

とにかく、パレードで雨が降るつもりはありませんが、新しいクールなアイデアを見つけたら、「堅牢性」とパフォーマンスの両方の観点から、物事の「古き良き方法」と照らし合わせて測定してください(キャッシング、おそらくあなたはパフォーマンスをにします;-)。

于 2009-11-19T03:12:41.670 に答える
1

私はいつもこのテクニックをメモ化に使用してきました。オブジェクトにメソッドが定義されている限り、関数ではないオブジェクトを呼び出すことができるという事実を使用します。__call__なんらかの理由で、フォールの背後にある方法も、アレックス・マルテッリの方法も私には思い浮かびませんでした。パフォーマンスはほぼ同じように機能するため、パフォーマンスはほぼ同じように機能すると思います。多分少し遅いです。しかし、リンクされたページが指摘しているように、「fib()の定義は、キャッシュコードがアルゴリズムを覆い隠すことなく、「明白な」ものになりました」。

于 2009-12-30T18:48:43.880 に答える