web-dev-qa-db-ja.com

Pythonラムダのローカル値へのバインド

次のコードは、1 2回、0 その後 1

def pv(v) :
  print v


def test() :
  value = []
  value.append(0)
  value.append(1)
  x=[]
  for v in value :
    x.append(lambda : pv(v))
  return x

x = test()
for xx in x:
  xx()

pythonラムダがローカル変数が指している参照にバインドするように見えます。しかし、そうではないようです。この問題は大規模システムで発生しました。 lambdaは、現代のC++のバインドと同等(たとえば 'boost :: bind')を実行しています。このような場合、スマートptrにバインドするか、またはラムダのコピーを構築します。

では、ローカル変数をラムダ関数にバインドし、使用時に正しい参照を保持するにはどうすればよいですか?ガベージコレクターを備えた言語からこれを期待することはないので、私はその振る舞いにかなり悩まされています。

問題のコードは次のようになります(l3_eは問題の原因となっている変数です)。

 for category in cat :
      for l2 in cat[category].entries :
        for l3 in cat[category].entries[l2].entry["sub_entries"] :
          l3_e = cat[category].entries[l2].entry["sub_entries"][l3]
          url = "http://forums.heroesofnewerth.com/" + l3_e.entry["url"]
          self.l4_processing_status[l3_e] = 0
          l3_discovery_requests.append( Request(
            url, callback = lambda response : self.parse_l4(response,l3_e)))
          print l3_e.entry["url"]
    return l3_discovery_requests
55
Hassan Syed

x.append(lambda : pv(v))x.append(lambda v=v: pv(v))に変更します。

「Pythonラムダが、ローカル変数が指し示す参照にバインドされ、背後で」と期待しますが、それはPythonが機能する方法ではありません。Pythonデフォルト引数は関数の呼び出し時ではなく作成時に評価されるため、デフォルト引数の使用は機能します。

これはラムダに関して特別なことではありません。考慮してください:

x = "before foo defined"
def foo():
    print x
x = "after foo was defined"
foo()

プリント

after foo was defined
85

ラムダのクロージャーは、その値ではなく、使用されている変数への参照を保持します。したがって、変数の値が後で変更されると、クロージャーの値も変更されます。つまり、クロージャー変数の値は、関数が作成されたときではなく、呼び出されたときに解決されます。 (ここでのPythonの動作は、関数型プログラミングの世界では珍しいことではありません。その価値はあります。)

2つの解決策があります。

  1. デフォルト引数を使用して、定義時に変数の現在の値をローカル名にバインドします。 lambda v=v: pv(v)

  2. ダブルラムダを使用し、すぐに最初のラムダを呼び出します。 (lambda v: lambda: pv(v))(v)

18
kindall