2014-01-10 13 views
14

私は匿名関数を使って遊びたかったので、単純なプライムファインダを作ることにしました。ここにある:私は、しかし、見つけることはlambda x:x%i==0iが毎回アクセスされることである変数の外側でλ関数を呼び出す

tests = [] 
end = int(1e2) 
i = 3 
while i <= end: 
    a = map(lambda f:f(i),tests) 
    if True not in a: 
     tests.append(lambda x:x%i==0) 
     print i 
    print tests 
    print "Test: "+str(i) 
    print str(a) 
    i+=2 

、私はそれがリテラルの数になりたいながら。代わりにlambda x:x%3==0になるにはどうすればいいですか?

+1

公式ドキュメントよりも詳しい説明については、[これらの厄介な閉鎖](http://code.activestate.com/recipes/502271/)を参照してください。しかし、短いバージョンでは、これらのテスト関数のそれぞれが同じ変数 'i'を囲むように効果的に閉じ込められ、 'i'は値を変更し続けます。 (これはかなり正確ではありません。なぜなら、グローバル変数は実際にクロージャーに格納する必要はありませんが、その効果は同じです)。 – abarnert

答えて

27

あなたは

lambda x, i=i: x%i==0 

ラムダを作成するときにこれは、それが作成されたときだったものは何でもiに等しいラムダのコンテキストでiを設定しますiを「捕獲」することができます。あなたが望むならば、それは正確にはキャプチャされていませんが、それはあなたが必要なものを得ることができますlambda x, n=i: x%n==0と言うことができます。このなし

、あなたが見てきたように、それは定義された機能で、以下に類似していたルックアップの問題で囲む範囲


iを探すために起こっている:

i = "original" 

def print_i1(): 
    print(i) # prints "changed" when called below 

def print_i2(s=i): #default set at function creation, not call 
    print(s) # prints "original" when called below 


i = "changed" 
print_i1() 
print_i2() 
+0

大丈夫です。 – user2864740

+0

+1とてもいい:) –

+2

これは、関数が*作成されたときにデフォルトの引数が評価されるため、関数が*呼び出されたときに変数の参照が行われるため、機能します。 –

2

ラムダを返す新しい関数を作成します。その後、それを呼び出して、iを引数として渡します。これにより、新しいバインディングスコープが作成されます。

def make_test (i): 
    # this i refers to the parameter (which evaluates to the /value/ passed) 
    return lambda x: x%i==0 

# .. 
# the /value/ resulting from evaluating the variable is passed 
tests.append(make_test(i)) 
6

testsのこれらの関数のそれぞれが変数iを参照しているという問題があります。

より一般的には、これは関数内で行います。この場合、These Nasty Closuresで説明されているように、クロージャーに格納される定義可能範囲の変数iがあります。

しかし、ここではさらに簡単です。iはグローバル変数であるため、クロージャはありません。この関数は実行時にグローバル変数としてiを検索するようにコンパイルされます。 iが変更されたため、関数は実行時に変更された値を表示します。そのような単純な。


(閉鎖やグローバルの両方で動作します)これを回避する伝統的な方法は、愛情を込めて、それは本当にハックではないにもかかわらず、「デフォルト値ハック」として知られています。 (the explanation in the FAQを参照してください。)ライアン・海寧の答えは、これを行う方法について説明します

lambda x, i=i: x%i==0 

は、これは、関数が作成された時点でのiの値に等しいデフォルト値で、iという名前のパラメータを作成します。次に、関数内で、パラメータiにアクセスすると、その値が取得されます。


あなたがJavaScriptのような言語に使用している場合より身近に見えるかもしれませんが、この周りの別の方法は、関数の作成関数を作成し、その機能 - の引数としてiの値を渡すことですuser2864740の答えのように、関数を作成:

(lambda i: lambda x: x%i)(i) 

これは、追加のパラメータ(誰かが偶然に引数を渡すことができること)で、関数のシグネチャを「汚染」回避が、関数の作成と呼び出しのコストで正当な理由がないために。この周り


第三の方法は、partialを使用することです。あなたがしようとしているのは、部分的に関数を適用する場合です。lambdaはラッパー関数を定義する代わりに、partialを使用するときれいにすることができます。

残念なことに、この場合、関数は演算子の中に隠され、それを公開する関数operator.modはキーワード引数をとらないため、2番目のオペランドを部分的に分けることはできません。だから、これはこの場合の悪い解決策です。あなたが本当にしたい場合、あなただけのより良い振る舞いラッパーを書くことができるとpartialこと:この場合

def opmod(a, b): 
    return a % b 

partial(operator.mod, b=i) 

、私はあなたが他のソリューションとのほうだと思います。 の場合は、頭に入れておいてください。

関連する問題