105

一連のGUIイベントとほぼ同じコールバック関数が必要です。関数は、どのイベントがそれを呼び出したかによって、わずかに異なる動作をします。私には単純なケースのように思えますが、ラムダ関数のこの奇妙な動作を理解することはできません。

したがって、以下の簡略化されたコードがあります。

def callback(msg):
    print msg

#creating a list of function handles with an iterator
funcList=[]
for m in ('do', 're', 'mi'):
    funcList.append(lambda: callback(m))
for f in funcList:
    f()

#create one at a time
funcList=[]
funcList.append(lambda: callback('do'))
funcList.append(lambda: callback('re'))
funcList.append(lambda: callback('mi'))
for f in funcList:
    f()

このコードの出力は次のとおりです。

mi
mi
mi
do
re
mi

私は期待しました:

do
re
mi
do
re
mi

なぜイテレータを使用すると物事が台無しになったのですか?

ディープコピーを使用してみました:

import copy
funcList=[]
for m in ('do', 're', 'mi'):
    funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
    f()

しかし、これには同じ問題があります。

4

9 に答える 9

147

ラムダが作成されるとき、ラムダは、それが使用する囲んでいるスコープ内の変数のコピーを作成しません。後で変数の値を検索できるように、環境への参照を維持します。たった1つmです。ループを通過するたびに割り当てられます。ループの後、変数のm値はになります'mi'。したがって、後で作成した関数を実際に実行すると、それmを作成した環境での値が検索され、それまでに値が設定されます'mi'

この問題に対する一般的で慣用的な解決策の1つはm、ラムダが作成されたときの値を、オプションのパラメーターのデフォルトの引数として使用して取得することです。通常は同じ名前のパラメーターを使用するため、コードの本文を変更する必要はありません。

for m in ('do', 're', 'mi'):
    funcList.append(lambda m=m: callback(m))
于 2009-06-02T08:27:06.053 に答える
86

ここでの問題は、m変数(参照)が周囲のスコープから取得されることです。ラムダスコープにはパラメーターのみが保持されます。

これを解決するには、ラムダの別のスコープを作成する必要があります。

def callback(msg):
    print msg

def callback_factory(m):
    return lambda: callback(m)

funcList=[]
for m in ('do', 're', 'mi'):
    funcList.append(callback_factory(m))
for f in funcList:
    f()

上記の例では、ラムダもサラウンドスコープを使用してを検索しますmが、今回は 呼び出しcallback_factoryごとに1回作成されるスコープです。callback_factory

またはfunctools.partialで:

from functools import partial

def callback(msg):
    print msg

funcList=[partial(callback, m) for m in ('do', 're', 'mi')]
for f in funcList:
    f()
于 2009-06-02T08:14:33.270 に答える
6

Pythonはもちろん参照を使用しますが、このコンテキストでは重要ではありません。

ラムダ(またはこれはまったく同じ動作であるため、関数)を定義すると、実行前にラムダ式が評価されません。

# defining that function is perfectly fine
def broken():
    print undefined_var

broken() # but calling it will raise a NameError

ラムダの例よりもさらに驚くべきこと:

i = 'bar'
def foo():
    print i

foo() # bar

i = 'banana'

foo() # you would expect 'bar' here? well it prints 'banana'

要するに、動的だと考えてください。解釈の前に何も評価されないため、コードは最新のmの値を使用します。

ラムダ実行でmを検索する場合、mは最上位のスコープから取得されます。つまり、他の人が指摘しているように、別のスコープを追加することで、この問題を回避できます。

def factory(x):
    return lambda: callback(x)

for m in ('do', 're', 'mi'):
    funcList.append(factory(m))

ここで、ラムダが呼び出されると、ラムダの定義スコープでxが検索されます。このxは、ファクトリの本体で定義されたローカル変数です。このため、ラムダ実行で使用される値は、ファクトリの呼び出し中にパラメーターとして渡された値になります。そしてドレミ!

注意として、factoryをfactory(m)[xをmに置き換える]と定義することもできますが、動作は同じです。わかりやすくするために別の名前を使用しました:)

アンドレイ・バウアーが同様のラムダ問題を抱えていることに気付くかもしれません。そのブログで興味深いのはコメントです。ここでは、Pythonクロージャについて詳しく知ることができます:)

于 2009-06-02T08:27:52.760 に答える
1

はい、それはスコープの問題です。ラムダ関数を使用しているかローカル関数を使用しているかに関係なく、外側のmにバインドされます。代わりに、ファンクターを使用してください。

class Func1(object):
    def __init__(self, callback, message):
        self.callback = callback
        self.message = message
    def __call__(self):
        return self.callback(self.message)
funcList.append(Func1(callback, m))
于 2009-06-02T08:25:41.987 に答える
1

ラムダへの解決策はよりラムダです

In [0]: funcs = [(lambda j: (lambda: j))(i) for i in ('do', 're', 'mi')]

In [1]: funcs
Out[1]: 
[<function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>]

In [2]: [f() for f in funcs]
Out[2]: ['do', 're', 'mi']

アウターlambdaは、の現在の値をにバインドするために使用さij ます

lambdaアウターが呼び出されるたびに、 asの現在の値lambdajバインドされたインナーのインスタンスが作成されます。ii

于 2017-07-13T23:42:33.123 に答える
0

ちなみに、mapは、有名なPythonの人物に軽蔑されていますが、この落とし穴を防ぐ構造を強制しています。

fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi'])

注意:最初lambda iは他の回答では工場のように機能します。

于 2012-09-07T09:26:57.183 に答える
0

Pythonには、実際には古典的な意味での変数はなく、該当するオブジェクトへの参照によってバインドされた名前だけがあります。関数でさえPythonのある種のオブジェクトであり、ラムダはルールの例外にはなりません:)

于 2009-06-02T08:37:56.733 に答える
0

まず、表示されているものは問題ではなく、参照による呼び出しや値による呼び出しとは関係ありません。

定義したラムダ構文にはパラメーターがないため、パラメーターで表示されるスコープmはラムダ関数の外部にあります。これが、これらの結果が表示されている理由です。

この例では、Lambda構文は必要ありません。むしろ、単純な関数呼び出しを使用することをお勧めします。

for m in ('do', 're', 'mi'):
    callback(m)

繰り返しになりますが、使用しているラムダパラメーターとそのスコープの開始位置と終了位置について非常に正確である必要があります。

補足として、パラメータの受け渡しについて。Pythonのパラメーターは、常にオブジェクトへの参照です。アレックス・マルテッリを引用するには:

用語の問題は、Pythonでは、名前の値がオブジェクトへの参照であるという事実が原因である可能性があります。したがって、常に値を渡し(暗黙のコピーはなし)、その値は常に参照になります。[...]「オブジェクト参照による」、「コピーされていない値による」など、その名前を作成したい場合は、私のゲストになります。「変数がボックスである」言語に「変数がポストイットタグである」言語に一般的に適用される用語を再利用しようとすると、私見では、助けるよりも混乱する可能性が高くなります。

于 2009-06-02T08:20:56.863 に答える
0

変数mがキャプチャされているため、ラムダ式には常にその「現在の」値が表示されます。

ある時点で値を効果的にキャプチャする必要がある場合は、関数を記述して、必要な値をパラメーターとして受け取り、ラムダ式を返します。その時点で、ラムダはパラメーターの値をキャプチャします。この値は、関数を複数回呼び出しても変更されません。

def callback(msg):
    print msg

def createCallback(msg):
    return lambda: callback(msg)

#creating a list of function handles with an iterator
funcList=[]
for m in ('do', 're', 'mi'):
    funcList.append(createCallback(m))
for f in funcList:
    f()

出力:

do
re
mi
于 2009-06-02T08:22:27.827 に答える