Python と Ruby の各循環参照変数の問題 (隠れたバグ?)

WBOY
リリース: 2016-06-16 08:44:00
オリジナル
1272 人が閲覧しました

私は Python でこの問題に遭遇しましたが、Ruby で説明する方が簡単です。 Ruby では、配列を走査する方法が数多くあります。最も一般的に使用される 2 つの方法は、for と each です。

コードをコピー コードは次のとおりです。 🎜>
arr = ['a', 'b', 'c']

arr.each { |e|
put e
}

for e in arr
places e
end
通常、私は後者のほうが好みです。見た目が書きやすいからだと思いますが、効率の観点からは、前者の方がわずかに速いはずです。後者は実際には、トラバーサルプロセス中に要素ごとにラムダ関数が呼び出されます。一般的には明らかではありませんが、コンテキストの設定と関数の呼び出しは、特に動的言語では確かにコストがかかります(JIT インライン化を考慮せずに最適化されている場合)。 )。しかし、今回の問題はパフォーマンスではありません。ただし、これは「each は要素ごとに新しいスコープを作成しますが、for は作成しません」と関係があります。
次のコードを見てください:


コードをコピーします コードは次のとおりです:
arr = ['a ', 'b' , 'c']
h1 = Hash.new
h2 = Hash.new

arr.each { |e|
h1[e] = lambda { e+ '!'}
}

for e in arr
h2[e] = lambda { e+'!' }
end

h1['a']。 call # => ?
h2['a'].call # => ?
2 つの呼び出しから何が得られるでしょうか?推測できたはずですよね?それらはそれぞれ「a!」と「c!」です。for はループの各ステップでスコープを再作成しないため、3 つのラムダのクロージャーは同じ変数を参照します。最後に値「c」が割り当てられ、この結果になりました。
この問題は、実際には私が Python で書いた小さなプログラムの一部に由来しています。コードは次のようなものです。


コードをコピーします コードは次のとおりです。次のように:
for prop in public_props:
setattr(proxy, 'get_%s'%prop, lambda: self.get_prop(prop))
ここでプロキシは私が提供したプロキシ オブジェクト。self のいくつかのパブリック属性を公開します。非パブリック属性へのアクセスを制限したいため、このプロキシには self への参照を格納したくありません。それ以外の場合は、アクセス許可の制限がない Python で、プロキシのようなものを使用できます。_orig_self.some_private_prop 経由でアクセスするのは簡単です。そこで私は最終的に上記のアプローチを選択しました。
残念ながら、今述べたように、for は毎回個別のスコープを作成しないため、クロージャーはすべて同じ変数を参照し、その結果、すべての属性値が最後の属性として取り出されます。このような奇妙なバグを見て、それが C/C++ にある場合は、メモリまたはポインタの問題であると疑う必要があります。しかし、長い間考えた後、ついに気づきました!しかし、Python には Ruby ほど便利なものはなく、lambda も使い物にならないので、最終的にはローカル関数を定義することで解決しました:


Copy code コードは次のとおりです:
def proxy_prop(name):
setattr(proxy, 'get_%s'%prop, lambda: self.get_prop(name)
for prop in public_props:
proxy_prop(prop)
最後に、先ほどの Ruby の例に関してもう 1 つ言いたいのですが、それぞれと for の実行順序を逆にすると、異なる結果が得られます。 :

コードをコピー コードは次のとおりです:arr = ['a', 'b', 'c']
h1 = Hash .new
h2 = Hash.new

for e in arr
h2[e] = lambda { e+'!' }
end

arr。 each { | e|
h1[e] = lambda { e+'!'}
}

h1['a'].call # => ['a '].call # => 'c!'

これで両方とも 'c!' になります!これは、Ruby 1.8 の実装では、ブロックのパラメータが通常のラムダ関数のパラメータのように単純ではなく、ローカル変数やグローバル変数などに代入できるためです。前の for ステートメントは現在のスコープにローカル変数として e を作成したため、この方法では参照されるたびに値が同じになり、隠れたバグが発生します。
ありがたいことに、ブロックのこの「機能」は Ruby 1.9 で削除され、ブロックのパラメーターは通常のパラメーターのみにできるため、この問題はもう存在しません。 1.9 ができるだけ早く普及することを願っています。
関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート