2012-04-06 7 views
1

私の意図は、キーがプリミティブであり、その値が文字列を返すゼロ引数の関数である辞書を作成することです。 (これは、VMを実装するためのより大きなプロジェクトの一部です)。これらの関数の中には、自明ではなく、手動で作成および割り当てられるものもあります。それらはうまく動作します。しかし、他のものは自動生成に適しているようです。python3を強制的に値渡しする方法はありますか?

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

>>> 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')] 

罰金、OK。ラムダ関数は呼び出されるまで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がやった時に変更されない独立変数を作成するだろうと思いました。それは事実ではないことが分かります。

この試みは明らかに機能しませんでした。たぶん、文字列上のコピーは、何もないですか?

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

右。文字列上のコピーは、無駄です。 Deepcopyはこれを修正しません。結果として、ラムダはまだ各ループで更新されている変数への参照を持ち、このバグを引き起こします。

私たちは別のアプローチが必要です。一時変数の静的変数に必要な変数を保存するとどうなりますか?各一時関数がそれ自身のアイデンティティを取得するなら、それはうまくいくはずです...

>>> 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' 

...と:この時点で、私は手動でそれをすべて扱うの危機に瀕してよ。しかし、これはあきらめているように感じ、信じられないほど退屈なものになります。この作業を達成するための適切な方法がありますか?結局のところ、私がいつもPythonを好きな理由の1つは、手動のポインタ管理から遠ざかることです。私はポインタツールの完全なスイートが含まれていることを望んでいたことは想像もしませんでした!

答えて

2

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

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

Pythonは関数だけのための新しいスコープを作成(およびクラスとモジュールを、それがここで問題ない)としては、ループ変数regは一度だけ存在し、したがって、すべてのクロージャは同じ変数を参照してください。したがって、ループの後に呼び出されると、変数が前提とする最後の値が表示されます。

tは同じですが、今度は共有されています。それぞれがr属性の正しい値を持つN個の別々のクロージャを作成します。しかし呼び出されると、囲みスコープ内でtが検索されるため、作成した最後のクロージャへの参照が常に得られます。

いくつかの回避策があります。(AB-)という事実は、デフォルトを使用している

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

別の可能性(terserが、よりあいまい):一つは、各閉鎖が参照するための新しい、専用のスコープを強制的に別の関数の中に閉鎖創造を推進しています

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

他のケースでは、あなたがfunctoolsを使用することができますが、ここに適用していないようです:パラメータは、定義時にバインドされています。

+0

ありがとうございます!デフォルトのパラメータを設定することは、make_const関数を通してすべてのものを与えることよりもはるかに意味があります。私は2つの方法の間にパフォーマンスに大きな違いはないと思いますか? – coriolinus

+0

@coriolinus大きな違いはありません。もしあれば、ボトルネックになる前に揚げる魚がもっと多いかもしれません。その時点で、弾丸を噛んで、コード[RPython](http://morepypy.blogspot.de/2011/04/tutorial-writing-interpreter-with-pypy.html)を作成し、コンパイルしてください。 C、JITコンパイラ、さまざまな優れたGCをほぼ無料で提供しています。 – delnan

関連する問題