Skip to content

Verhalten der Funktion exec in Python 2 und Python 3

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_LocalsToFastzurü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 Funktion exec() 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, dann exec das interne Wörterbuch (dasjenige, das von locals()) und versetzt es nicht in seinen ursprünglichen Zustand zurück. Ein Aufruf von locals() aktualisiert das Wörterbuch (wie in Abschnitt 2 der Python-Dokumentation beschrieben), und der Wert, der in exec wird vergessen.
    Die Notwendigkeit des Aufrufs von locals() 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 von exec 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, dann exec das interne Wörterbuch ändern, es sei denn, die Variable wird danach gesetzt. Es scheint einen Fehler in der Art und Weise zu geben, wie locals() das Wörterbuch aktualisiert; dieser Fehler ermöglicht den Zugriff auf den Wert, der in exec durch den Aufruf von locals() nach exec.

Um es zusammenzufassen:

  • Es gibt weder in Python 2 noch in Python 3 einen Fehler.
  • Das unterschiedliche Verhalten von exec rührt von exec 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 singletonSingleton, 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()), denn d und locals() 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).
  • 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']
      (wobei LOCALS der interne lokale Bereich sein soll)
    • Falls nicht bereits vorhanden, erstellen Sie SINGLETON Objekt
    • aktualisieren. SINGLETONso referenziert es alle Einträge in LOCALS
    • 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 Variable a oder LOCALS['a'] immer noch Number(1) ist
    • Jetzt, locals() wieder aufgerufen wird, ist die SINGLETON wird aktualisiert.
    • Als d verweist auf SINGLETON, nicht LOCALS, d ändert sich auch!

Mehr zu diesem überraschenden Detail, warum 1 ein Singleton ist, während 300 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 execzu 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.

Hier sind die Rezensionen und Bewertungen



Nutzen Sie unsere Suchmaschine

Suche
Generic filters

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.