369 Stimmen

Was erfassen Lambda-Funktionsabschlüsse?

Kürzlich habe ich angefangen, mit Python herumzuspielen und bin dabei auf etwas Seltsames in der Funktionsweise von Closures gestoßen. Betrachten Sie den folgenden Code:

adders=[None, None, None, None]

for i in [0,1,2,3]:
   adders[i]=lambda a: i+a

print adders[1](3)

Es bildet eine einfache Reihe von Funktionen, die eine einzelne Eingabe annehmen und diese um eine Zahl erhöht zurückgeben. Die Funktionen werden konstruiert in for Schleife, in der der Iterator i läuft von 0 a 3 . Für jede dieser Zahlen a lambda wird eine Funktion erstellt, die Folgendes erfasst i und fügt es der Eingabe der Funktion hinzu. Die letzte Zeile ruft die zweite lambda Funktion mit 3 als Parameter. Zu meiner Überraschung war die Ausgabe 6 .

Ich hatte eine 4 . Meine Überlegung war: In Python ist alles ein Objekt und somit ist jede Variable im Wesentlichen ein Zeiger auf dieses Objekt. Beim Erstellen des lambda Verschlüsse für i erwartet, dass er einen Zeiger auf das Integer-Objekt speichert, auf das derzeit durch i . Das bedeutet, dass, wenn i ein neues Integer-Objekt zugewiesen wird, sollte dies keine Auswirkungen auf die zuvor erstellten Verschlüsse haben. Leider ist die Inspektion der adders Array in einem Debugger zeigt, dass dies der Fall ist. Alle lambda Funktionen beziehen sich auf den letzten Wert von i , 3 was zu folgenden Ergebnissen führt adders[1](3) zurückkehrend 6 .

Das wirft bei mir folgende Fragen auf:

  • Was genau wird mit den Verschlüssen erfasst?
  • Was ist der eleganteste Weg, um die lambda Funktionen zur Erfassung des aktuellen Wertes von i in einer Weise, die nicht beeinträchtigt wird, wenn i seinen Wert ändert?

2voto

Chris Lutz Punkte 69879

Um Ihre zweite Frage zu beantworten: Der eleganteste Weg, dies zu tun, wäre die Verwendung einer Funktion, die zwei Parameter anstelle eines Arrays benötigt:

add = lambda a, b: a + b
add(1, 3)

Allerdings ist die Verwendung von lambda hier ein wenig albern. Python gibt uns die operator Modul, das eine funktionale Schnittstelle zu den Basisoperatoren bietet. Das obige Lambda hat unnötigen Overhead, nur um den Additionsoperator aufzurufen:

from operator import add
add(1, 3)

Ich verstehe, dass Sie herumspielen und versuchen, die Sprache zu erforschen, aber ich kann mir keine Situation vorstellen, in der ich ein Array von Funktionen verwenden würde, bei denen Python's scoping weirdness im Weg stehen würde.

Wenn Sie wollten, könnten Sie eine kleine Klasse schreiben, die Ihre Array-Indexierungssyntax verwendet:

class Adders(object):
    def __getitem__(self, item):
        return lambda a: a + item

adders = Adders()
adders[1](3)

-1voto

Joffan Punkte 1414

Eine Möglichkeit zur Abgrenzung des Umfangs der i besteht darin, das Lambda in einem anderen Bereich (einer Schließungsfunktion) zu erzeugen und ihm die notwendigen Parameter zu übergeben, um das Lambda zu erzeugen:

def get_funky(i):
    return lambda a: i+a

adders=[None, None, None, None]

for i in [0,1,2,3]:
   adders[i]=get_funky(i)

print(*(ar(5) for ar in adders))

Geben 5 6 7 8 Natürlich.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X