Nach so viel Kämpfen haben wir endlich die Antwort auf diese Frage gefunden, die einige Benutzer dieses Raums präsentieren. Wenn Sie etwas teilen möchten, hören Sie nicht auf, Ihren Kommentar zu teilen.
Lösung:
Es gibt einen großen Unterschied zwischen exec
in Python 2 und exec()
in Python 3. Sie behandeln exec
als eine Funktion, aber in Wirklichkeit ist es eine Anweisung in Python 2.
Aufgrund dieses Unterschieds können Sie in Python 3 lokale Variablen im Funktionsumfang nicht ändern, indem Sie exec
ändern, auch wenn dies in Python 2 möglich war. Nicht einmal vorher deklarierte Variablen.
locals()
spiegelt lokale Variablen nur in eine Richtung. Das Folgende hat weder in 2 noch in 3 funktioniert:
def foo():
a = 'spam'
locals()['a'] = 'ham'
print(a) # prints 'spam'
In Python 2 war die Verwendung der exec
Anweisung bedeutete, dass der Compiler wusste, dass er die Optimierungen des lokalen Bereichs ausschalten musste (Umschalten von LOAD_FAST
zu LOAD_NAME
zum Beispiel, um Variablen sowohl im lokalen als auch im globalen Bereich nachzuschlagen). Mit exec()
eine Funktion ist, steht diese Option nicht mehr zur Verfügung und Funktionsbereiche sind jetzt immer optimiert.
Außerdem ist in Python 2 die exec
Anweisung explizit alle Variablen, die in locals()
gefundenen Variablen zurück in die Funktionslokale mit PyFrame_LocalsToFast
zurück, allerdings nur, wenn keine Globale und locals Parameter geliefert wurden.
Die richtige Abhilfe besteht darin, einen neuen Namensraum (ein Wörterbuch) für Ihre exec()
Aufruf zu verwenden:
def execute(a, st):
namespace = {}
exec("b = {}nprint('b:', b)".format(st), namespace)
print(namespace['b'])
Die exec()
Dokumentation ist sehr explizit über diese Einschränkung:
Anmerkung: Der Standard Lokale verhalten sich wie für die Funktion
locals()
unten beschrieben: Änderungen am Standard locals Wörterbuch sollten nicht versucht werden. Übergeben Sie ein explizites locals Wörterbuch, wenn Sie die Auswirkungen des Codes auf locals nach der Funktionexec()
zurückgibt.
Ich würde sagen, es ist ein Fehler von Python3.
def u():
exec("a=2")
print(locals()['a'])
u()
gibt "2" aus.
def u():
exec("a=2")
a=2
print(a)
u()
druckt "2".
Aber .
def u():
exec("a=2")
print(locals()['a'])
a=2
u()
scheitert mit
Traceback (most recent call last):
File "", line 1, in
File "", line 3, in u
KeyError: 'a'
--- EDIT ---
Ein weiteres interessantes Verhalten:
def u():
a=1
l=locals()
exec("a=2")
print(l)
u()
def u():
a=1
l=locals()
exec("a=2")
locals()
print(l)
u()
gibt aus.
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 1}
Und auch
def u():
l=locals()
exec("a=2")
print(l)
print(locals())
u()
def u():
l=locals()
exec("a=2")
print(l)
print(locals())
a=1
u()
gibt aus.
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}}
Offensichtlich wird die Aktion von exec
auf Einheimische wie folgt:
- Wenn eine Variable innerhalb von
exec
gesetzt wird und diese Variable eine lokale Variable war, dannexec
das interne Wörterbuch (dasjenige, das vonlocals()
) und versetzt es nicht in seinen ursprünglichen Zustand zurück. Ein Aufruf vonlocals()
aktualisiert das Wörterbuch (wie in Abschnitt 2 der Python-Dokumentation beschrieben), und der Wert, der inexec
wird vergessen.
Die Notwendigkeit des Aufrufs vonlocals()
aufzurufen, um das Wörterbuch zu aktualisieren, ist kein Fehler von python3, da es dokumentiert ist, aber es ist nicht intuitiv. Außerdem ist die Tatsache, dass Änderungen von Locals innerhalb vonexec
die Locals der Funktion nicht ändern, ist ein dokumentierter Unterschied zu Python2 (die Dokumentation sagt "Übergeben Sie ein explizites Locals-Verzeichnis, wenn Sie die Auswirkungen des Codes auf die Locals nach der Rückkehr der Funktion exec() sehen müssen"), und ich bevorzuge das Verhalten von Python2. - Wenn eine Variable innerhalb von
exec
gesetzt wird und diese Variable vorher nicht existierte, dannexec
das interne Wörterbuch ändern, es sei denn, die Variable wird danach gesetzt. Es scheint einen Fehler in der Art und Weise zu geben, wielocals()
das Wörterbuch aktualisiert; dieser Fehler ermöglicht den Zugriff auf den Wert, der inexec
durch den Aufruf vonlocals()
nachexec
.
Um es zusammenzufassen:
- Es gibt weder in Python 2 noch in Python 3 einen Fehler.
- Das unterschiedliche Verhalten von
exec
rührt vonexec
in Python 2 eine Anweisung war, während es in Python 3 eine Funktion wurde.
Bitte beachten:
Ich erzähle hier nichts Neues. Dies ist nur eine Zusammenstellung der Wahrheit
die in all den anderen Antworten und Kommentaren zu finden ist.
Alles, was ich hier versuche, ist, Licht in einige der obskureren Details zu bringen.
Der einzige Unterschied zwischen Python 2 und Python 3 ist, dass in der Tat, exec
in Python 2 in der Lage ist, den lokalen Bereich der umschließenden Funktion zu ändern (weil es eine Anweisung ist und auf den aktuellen lokalen Bereich zugreifen kann) und dies in Python 3 nicht mehr tun kann (weil es jetzt eine Funktion ist, also in ihrem eigenen lokalen Bereich läuft).
Die Irritation hat aber nichts mit dem exec
Anweisung zu tun, sondern nur mit einem speziellen Verhaltensdetail:
locals()
gibt etwas zurück, das ich als "a scope-wise mutable singleton which, after the call to locals()
immer nur alle Variablen im lokalen Bereich referenziert".
Bitte beachten Sie, dass das Verhalten von locals()
sich zwischen Python 2 und 3 nicht geändert hat. Also, dieses Verhalten zusammen mit der Änderung, wie exec
sieht aus, als sei es unberechenbar, ist es aber nicht, da es nur ein Detail aufdeckt, das schon immer da war.
Was bedeutet "ein vom Umfang her veränderbares Singleton, das Variablen im lokalen Bereich referenziert"?
- Es ist ein
scope-wise singleton
Singleton, denn unabhängig davon, wie oft man es aufruft.locals()
im gleichen Bereich aufruft, ist das zurückgegebene Objekt immer dasselbe.- Daher die Beobachtung, dass
id(d) == id(locals())
, dennd
undlocals()
beziehen sich auf das gleiche Objekt, das gleiche Singleton, da es nur eines geben kann (in einem anderen Bereich erhält man ein anderes Objekt, aber im gleichen Bereich sieht man nur dieses eine).
- Daher die Beobachtung, dass
- Es ist
mutable
, da es ein normales Objekt ist, also kann man es verändern.locals()
erzwingt, dass alle Einträge des Objekts wieder auf die Variablen im lokalen Bereich verweisen.- Wenn Sie etwas im Objekt ändern (über
d
), ändert dies das Objekt, da es ein normales veränderbares Objekt ist.
-
Diese Änderungen des Singletons pflanzen sich nicht in den lokalen Bereich zurück, da alle Einträge im Objekt
references to the variables in the local scope
. Wenn du also Einträge änderst, ändert dies das Singleton-Objekt und nicht den Inhalt der Stelle, auf die "die Referenzen zeigten, bevor du die Referenz ändertest" (daher änderst du nicht die lokale Variable).-
In Python sind Strings und Numbers nicht veränderbar. Das heißt, wenn man einem Eintrag etwas zuweist, ändert man nicht das Objekt, auf das der Eintrag zeigt, sondern man führt ein neues Objekt ein und weist dem Eintrag eine Referenz darauf zu. Beispiel:
a = 1 d = locals() d['a'] = 300 # d['a']==300 locals() # d['a']==1
Neben der Optimierung tut dies:
- Neues Objekt Number(1) erzeugen - was übrigens ein anderes Singleton ist.
- Zeiger auf dieses Number(1) speichern in
LOCALS['a']
(wobeiLOCALS
der interne lokale Bereich sein soll) - Falls nicht bereits vorhanden, erstellen Sie
SINGLETON
Objekt - aktualisieren.
SINGLETON
so referenziert es alle Einträge inLOCALS
- speichert den Zeiger der
SINGLETON
in .LOCALS['d']
- Number(300) erstellen, das ist nicht ein Singleton, BTW.
- Zeiger auf diese Number(300) speichern in
d['a']
- daher die
SINGLETON
ebenfalls aktualisiert wird. - aber
LOCALS
ist . nicht aktualisiert,
daher wird die lokale Variablea
oderLOCALS['a']
immer noch Number(1) ist - Jetzt,
locals()
wieder aufgerufen wird, ist dieSINGLETON
wird aktualisiert. - Als
d
verweist aufSINGLETON
, nichtLOCALS
,d
ändert sich auch!
-
Mehr zu diesem überraschenden Detail, warum
1
ein Singleton ist, während300
nicht ist, siehe https://stackoverflow.com/a/306353.Aber bitte nicht vergessen: Zahlen sind unveränderlich, wenn Sie also versuchen, eine Zahl in einen anderen Wert zu ändern, erzeugen Sie effektiv ein anderes Objekt.
Fazit:
Sie können die exec
Verhalten von Python 2 auf Python 3 übertragen (außer man ändert seinen Code), da es keine Möglichkeit mehr gibt, die lokalen Variablen außerhalb des Programmflusses zu verändern.
Allerdings kann man das Verhalten von Python 3 auf Python 2 übertragen, so dass man heute Programme schreiben kann, die gleich laufen, egal ob sie mit Python 3 oder Python 2 laufen. Das liegt daran, dass man in (neuerem) Python 2 mit exec
auch mit funktionsähnlichen Argumenten verwenden kann (in der Tat ist das ein 2- oder 3-Tupel), was es erlaubt, die gleiche Syntax mit der gleichen Semantik zu verwenden, die aus Python 3 bekannt ist:
exec "code"
(was nur in Python 2 funktioniert) wird zu (was für Python 2 und 3 funktioniert):
exec("code", globals(), locals())
Aber Achtung, dass "code"
auf diese Weise nicht mehr den lokalen, umschließenden Bereich verändern kann. Siehe auch https://docs.python.org/2/reference/simple_stmts.html#exec.
Noch ein paar letzte Worte:
Die Änderung von exec
in Python 3 ist gut. Wegen der Optimierung.
In Python 2 war es nicht möglich, eine Optimierung über exec
zu optimieren, weil sich der Zustand aller lokalen Variablen, die unveränderliche Inhalte enthielten, unvorhersehbar ändern konnte. Das kann jetzt nicht mehr passieren. Jetzt gelten die üblichen Regeln für Funktionsaufrufe für exec()
wie für alle anderen Funktionen auch.