2009-06-02 6 views
62

一連の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 

物事?

私はdeepcopyを使用して試した

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

しかし、これは同じ問題を抱えています。

+2

あなたの質問のタイトルはやや誤解を招きます。 – lispmachine

+1

lambdaを混乱させるのはなぜですか?関数を定義するのにdefを使わないのはなぜですか?ラムダをとても重要にするあなたの問題についてはどうですか? –

+0

@ S.Lottネストされた関数は同じ問題を引き起こします(もっと明瞭に見えるかもしれません)。 – lispmachine

答えて

56

ここでの問題は、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を見つけるために、suroundingスコープを使用していますが、それはあたり一度作成されcallback_factoryスコープのこの 時間毎回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() 
+1

この説明は少し誤解を招く。問題は、反復におけるmの値の変更であり、範囲ではありません。 – Ixx

+0

functoolsの+1 – Dacav

0

まず、何を見ていることは問題ではありません、およびコール参照によってするかによって、値は関係ありません。

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

ラムダ構文は、あなたの例では必要ありません、あなたはかなり単純な関数呼び出し使用することになります。もう一度、あなたが使用して場所を正確に自分のしているものラムダパラメータに関する非常に正確でなければなりません

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

をスコープの開始と終了。

注意事項として、パラメータ渡しに関して。 Pythonのパラメータは、常にオブジェクトへの参照です。アレックスマルテッリを引用する:

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

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 
0

はい、それはスコープの問題ですラムダ関数かローカル関数かを問わず、外側の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)) 
107

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

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

for m in ('do', 're', 'mi'): 
    funcList.append(lambda m=m: callback(m)) 
+0

+1の面白いデフォルトパラメータの過剰使用 – lispmachine

+0

平均値*オプションのパラメータ*デフォルト値 – lispmachine

+6

良い解決策!トリッキーですが、私は元の意味が他の構文よりもはっきりしていると感じています。 – Quantum7

4

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)[replace x by m]として定義できましたが、動作は同じです。私は明確に別の名前を使用しました:)

Andrej Bauerには同じようなラムダの問題があります。そのブログで興味深いのは、Pythonのクロージャについての詳細を学ぶコメントです。

0

Pythonの古典的な意味で実際に変数はありません。適用可能なオブジェクトへの参照によってバインドされた名前だけです。関数もPythonでは何らかのオブジェクトであり、ラムダはルールを例外にしません:)

+0

「古典的な意味で」と言うと、「Cのように」という意味です。 Pythonを含む多くの言語がC言語とは異なる変数を実装しています。 –

1

Fredrik Lundhによると、手ごわい問題に直接関係はありませんが、貴重な知恵はPython Objectsです。

+0

あなたの答えに直接関係しませんが、子猫を検索します:http://www.google.com/search?q=kitten – Singletoned

+0

@Singletoned:OPがgrokked私がリンクを提供した記事は、最初に質問しませんでした。それが間接的に関係する理由です。子猫が私の答えに間接的に関連しているかどうかを私に説明することができて嬉しいです(ホリスティックなアプローチで、私は推測します) – tzot

0

補足として、mapは、よく知られているPythonの図で軽蔑されていますが、この落とし穴を防ぐ構造になっています。

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

NB:最初のlambda iは、他の回答では工場のように動作します。

0

ラムダにsoluitonよりラムダ

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外側lambdaは、それがインスタンスを作成呼ばれる

で毎回i jへの現在の値をバインドするために使用されていますlambdajの現在の値がiの値に連結されています。iの値