Okay, nehmen Sie das mit, ich weiß, es wird schrecklich verworren aussehen, aber bitte helfen Sie mir zu verstehen, was passiert.
from functools import partial
class Cage(object):
def __init__(self, animal):
self.animal = animal
def gotimes(do_the_petting):
do_the_petting()
def get_petters():
for animal in ['cow', 'dog', 'cat']:
cage = Cage(animal)
def pet_function():
print "Mary pets the " + cage.animal + "."
yield (animal, partial(gotimes, pet_function))
funs = list(get_petters())
for name, f in funs:
print name + ":",
f()
Gibt:
cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.
Warum bekomme ich nicht drei verschiedene Tiere? Ist das nicht cage
in den lokalen Bereich der verschachtelten Funktion 'gepackt'? Wenn nicht, wie sucht ein Aufruf der verschachtelten Funktion die lokalen Variablen?
Ich weiß, dass solche Probleme normalerweise bedeuten, dass man es falsch macht, aber ich würde gerne verstehen, was passiert.
for animal in ['cat', 'dog', 'cow']
... Ich bin sicher, jemand wird mitkommen und dies erklären - es ist eines dieser Python-Fallstricke :)Antworten:
Die verschachtelte Funktion sucht bei der Ausführung nach Variablen aus dem übergeordneten Bereich, nicht nach der Definition.
Der Funktionskörper wird kompiliert und die 'freien' Variablen (nicht in der Funktion selbst durch Zuweisung definiert) werden überprüft und dann als Abschlusszellen an die Funktion gebunden, wobei der Code einen Index verwendet, um auf jede Zelle zu verweisen.
pet_function
hat also eine freie Variable (cage
), auf die dann über eine Abschlusszelle, Index 0, verwiesen wird. Der Abschluss selbst zeigt auf die lokale Variablecage
in derget_petters
Funktion.Wenn Sie die Funktion tatsächlich aufrufen, wird dieser Abschluss verwendet, um den Wert von
cage
im umgebenden Bereich zum Zeitpunkt des Aufrufs der Funktion anzuzeigen . Hier liegt das Problem. Wenn Sie Ihre Funktionen aufrufen,get_petters
ist die Berechnung der Ergebnisse bereits abgeschlossen. Diecage
lokale Variable an einem gewissen Punkt während dieser Ausführung wurde jede der zugeordneten'cow'
,'dog'
und'cat'
Zeichenfolge, aber am Ende der Funktioncage
enthält, die letzten Wert'cat'
. Wenn Sie also jede der dynamisch zurückgegebenen Funktionen aufrufen, wird der Wert'cat'
gedruckt.Die Problemumgehung besteht darin, sich nicht auf Verschlüsse zu verlassen. Sie können stattdessen eine Teilfunktion verwenden, einen neuen Funktionsbereich erstellen oder die Variable als Standardwert für einen Schlüsselwortparameter binden .
Teilfunktionsbeispiel mit
functools.partial()
:Erstellen eines neuen Bereichsbeispiels:
Binden der Variablen als Standardwert für einen Schlüsselwortparameter:
Die
scoped_cage
Funktion in der Schleife muss nicht definiert werden. Die Kompilierung erfolgt nur einmal und nicht bei jeder Iteration der Schleife.quelle
Nach meinem Verständnis wird im Namespace der übergeordneten Funktion nach Käfig gesucht, wenn die angegebene pet_function tatsächlich aufgerufen wird, nicht vorher.
Also wenn du es tust
Sie generieren 3 Funktionen, die den zuletzt erstellten Käfig finden.
Wenn Sie Ihre letzte Schleife ersetzen durch:
Sie erhalten tatsächlich:
quelle
Dies ergibt sich aus dem Folgenden
Nach der Iteration wird der Wert von
i
träge als Endwert gespeichert.Als Generator würde die Funktion funktionieren (dh jeden Wert nacheinander drucken), aber wenn sie in eine Liste umgewandelt wird, läuft sie über den Generator , daher werden alle Aufrufe von
cage
(cage.animal
) Katzen zurückgeben.quelle
Vereinfachen wir die Frage. Definieren:
Dann bekommen wir genau wie in der Frage:
Aber wenn wir es vermeiden, eine
list()
erste zu erstellen :Was ist los? Warum verändert dieser subtile Unterschied unsere Ergebnisse vollständig?
Wenn wir uns das ansehen
list(get_petters())
, wird aus den sich ändernden Speicheradressen deutlich, dass wir tatsächlich drei verschiedene Funktionen liefern:Schauen Sie sich jedoch die
cell
s an, an die diese Funktionen gebunden sind:Für beide Schleifen
cell
bleibt das Objekt während der Iterationen gleich. Wie erwartetstr
variiert die spezifische Referenz in der zweiten Schleife. Dascell
Objekt bezieht sich aufanimal
das Objekt , das beimget_petters()
Aufruf erstellt wird. Jedochanimal
ändert sich, wasstr
Objekt , das es sich bezieht , wie die Generatorfunktion läuft .In der ersten Schleife erstellen wir während jeder Iteration alle
f
s, rufen sie jedoch erst auf, wenn der Generatorget_petters()
vollständig erschöpft ist und bereits einelist
der Funktionen erstellt wurde.In der zweiten Schleife halten wir während jeder Iteration den
get_petters()
Generator an und rufenf
nach jeder Pause auf. Auf diese Weise erhalten wir den Wertanimal
zu dem Zeitpunkt, zu dem die Generatorfunktion angehalten wurde.Wie @Claudiu eine Antwort auf eine ähnliche Frage gibt :
quelle