1

私の意図は、キーがプリミティブであり、値が文字列を返すゼロ引数関数である辞書を作成することです。(これは、VMを実装するためのより大きなプロジェクトの一部です。)これらの機能の一部は重要であり、手動で作成および割り当てられます。それらはうまく機能します。ただし、自動生成に適しているように見えるものもあります。

私の最初の試みは失敗しました:

>>> regs = ['a', 'b', 'c', 'x', 'y', 'z']
>>> vals = {i : lambda: r for i, r in enumerate(regs)}
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]

いいよ; ラムダ関数は、呼び出されるまでrを読み取りません。値を単独で分離しようとして、もう一度試しました。

>>> from copy import copy
>>> vals = {}
>>> i = 0
>>> for reg in regs:
...     r = copy(reg)  # (1)
...     vals[i] = lambda: r
...     i += 1
...
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]

(1)このステップにより、regが変更されても変更されない独立変数が作成されると思いました。そうではないことが判明しました。

そのため、その試みは明らかに機能しませんでした。たぶん、文字列上のコピーは、noopですか?

>>> 's' is 's'
True
>>> a = 's'
>>> b = copy(a)
>>> a is b
True
>>> from copy import deepcopy
>>> b = deepcopy(a)
>>> a is b
True

右。文字列のコピーは、noopです。Deepcopyはこれを修正しません。その結果、ラムダにはまだ各ループで更新されている変数への参照があり、このバグが発生します。

別のアプローチが必要です。必要な変数を一時関数の静的変数に保存するとどうなりますか?各一時関数が独自のIDを取得する場合、これは機能するはずです...

>>> vals = {}
>>> i = 0
>>> for reg in regs:
...     def t():
...             return t.r
...     t.r = reg
...     vals[i] = t
...     i += 1
...
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]

いいえ。この時点で、私はすべてを手動で処理する寸前です。

>>> vals = {}
>>> vals[0] = lambda: 'a'
>>> vals[1] = lambda: 'b'

...など。しかし、これはあきらめているように感じ、信じられないほど退屈になります。このタスクを実行するための適切なpythonicの方法はありますか?結局のところ、私が通常pythonを愛する理由の1つは、手動のポインター管理から離れることができることです。ポインタツールの完全なスイートが含まれているとは想像もしていませんでした。

4

1 に答える 1

2

クロージャーは決してコピーしません。値も参照もコピーしません。代わりに、使用するスコープと変数を記憶し、常にそこに戻ります。同じことが、C#、JavaScript、IIRC Lisp など、他のいくつかの言語にも当てはまります。同じことが他のいくつかの言語にも当てはまります。これは、いくつかの高度なユース ケース (基本的には、複数のクロージャで状態を共有する場合) では重要ですが、1 つを噛む可能性があります。例えば:

x = 1
def f(): return x
x = 2
assert f() == 2

Python は関数 (およびクラスとモジュールですが、ここでは関係ありません) の新しいスコープのみを作成するため、ループ変数regは 1 回しか存在しないため、すべてのクロージャが同じ変数を参照します。したがって、ループの後に呼び出されると、変数が想定する最後の値が表示されます。

同じことが当てはまりますが、今回だけ共有されるのは、それぞれの属性tに適切な値を持つ N 個の個別のクロージャーを作成することです。rただし、呼び出されると、t囲んでいるスコープを検索するため、作成した最後のクロージャへの参照が常に取得されます。

いくつかの回避策があります。1 つは、クロージャーの作成を別の関数にプッシュすることです。これにより、各クロージャーが参照する新しい専用スコープが強制されます。

def make_const(x):
    def const():
        return x
    return const

もう 1 つの可能性 (簡潔ではありますが、あいまいです) は、既定のパラメーターが定義時にバインドされるという事実を (ab-) 使用することです。

for reg in regs:
    t = lambda reg=reg: reg

それ以外の場合は を使用できますがfunctools、ここでは適用されないようです。

于 2012-04-06T11:24:56.350 に答える