2014-01-10 13 views
14

Ich wollte mit anonymen Funktionen spielen, also entschied ich mich, einen einfachen Prime-Finder zu machen. Hier ist sie:Lambda-Funktion außerhalb der Variablen

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 

Was finde ich jedoch, dass die i in den lambda x:x%i==0 jedes Mal zugegriffen wird, während ich es möchte eine wörtliche Zahl sein. Wie kann ich es stattdessen lambda x:x%3==0 bekommen?

+1

Siehe auch [Diese Nasty Closures] (http://code.activestate.com/recipes/502271/) für eine bessere Erklärung als die in der offiziellen Dokumentation. Aber die kurze Version ist, dass jede dieser Testfunktionen effektiv eine Schließung um die gleiche Variable "i" ist und "i" den Wert ständig ändert. (Dies ist nicht ganz korrekt, weil globale Variablen nicht wirklich in Schließungen gespeichert werden müssen, aber der Effekt ist der gleiche.) – abarnert

Antwort

27

Sie können "erfassen" die i beim Erstellen der Lambda-

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

Dies wird die i im Kontext des Lambda gleich, was auch immer i war, als es erstellt wurde. Sie könnten auch sagen lambda x, n=i: x%n==0 wenn Sie wollten, ist es nicht genau erfassen, aber es bekommt Sie, was Sie brauchen.

Ohne diese, wie Sie gesehen haben, ist es für einen i im umgebenden Gültigkeitsbereich suchen geht


Es ist eine Frage der Lookup, die mit definierten Funktionen an folgende analog ist:

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

Ooh, clever. – user2864740

+0

+1 Sehr nett :) –

+2

Dies funktioniert, weil Standardargumente ausgewertet werden, wenn die Funktion * erstellt * ist, während Variablensuche durchgeführt wird, wenn die Funktion * als * aufgerufen wird. –

2

Erstellen Sie eine neue Funktion, die das Lambda zurückgibt. Dann rufe das an und gebe i als Argument ein. Dies wird einen neuen Bindungsbereich erstellen.

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

Das Problem ist, dass jede dieser Funktionen in tests bezieht sich auf die Variable i.

Üblicherweise machen Sie dies innerhalb einer Funktion, in diesem Fall haben Sie eine Variable i, die in einem Abschluss gespeichert wird, wie in These Nasty Closures erklärt.

Aber hier ist es noch einfacher: i ist eine globale Variable, also gibt es keine Schließung. Die Funktionen werden kompiliert, um i als eine globale Variable zu suchen, wenn sie ausgeführt wird. Da sich i geändert hat, sehen die Funktionen den geänderten Wert, wenn sie ausgeführt werden. So einfach ist das.


Der traditionelle Weg, um diese als „default-Wert Hack“ liebevoll genannt wird (die für beide Verschlüsse und Globals funktioniert), auch wenn es nicht wirklich ein Hack ist. (Siehe the explanation in the FAQ.) Ryan Haining Antwort erklärt, wie dies zu tun:

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

Dies erzeugt einen Parameter i, mit einem Standardwert gleich den Wert von i zu der Zeit genannt die Funktion erstellt wird. Dann innerhalb der Funktion, wenn Sie auf Parameter i zugreifen, und Sie erhalten diesen Wert.


Eine andere Möglichkeit, um dieses, die besser vertraut zu sein scheint, wenn Sie Sprachen wie JavaScript verwenden sind, ist eine Funktion schöpf Funktion, zu erstellen und den Wert von i als Argument an, dass funktions- übergeben Funktion zu schaffen, wie es in user2864740 Antwort:

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

Dies vermeidet die Signatur der Funktion mit einem zusätzlichen Parameter „verschmutzen“, aber auf Kosten der Erstellung und den Aufruf einer Funktion (dass jemand aus Versehen ein Argument passieren könnte) aus keinem guten Grund.


Ein dritter Weg ist partial zu verwenden. In Fällen, in denen Sie nur versuchen, eine Funktion teilweise anzuwenden, verwenden partial anstatt eine Wrapper-Funktion als lambda definieren kann sauberer sein.

Leider ist in diesem Fall die Funktion in einem Operator verborgen, und die Funktion operator.mod, die es verfügbar macht, nimmt keine Schlüsselwortargumente, so dass Sie seinen zweiten Operanden nicht sinnvoll partiell verwenden können. Also, das ist in diesem Fall eine schlechte Lösung. Wenn Sie wirklich wollte, könnte man nur einen Wrapper schreiben, die besser und partial verhält sich das:

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

partial(operator.mod, b=i) 

In diesem Fall, ich glaube, du bist besser mit den anderen Lösungen aus; behalte es einfach in deinem Kopf für Fälle, in denen es geeignet ist.

Verwandte Themen