Zunächst möchte ich sagen, dass ich die vielen Threads mit ähnlichen Themen zum Erstellen von dynamisch benannten Variablen gelesen habe, aber sie beziehen sich hauptsächlich auf Python 2 oder gehen davon aus, dass Sie mit Klassen arbeiten. Und ja, ich lese Verhalten der exec-Funktion in Python 2 und Python 3 .
Ich bin mir auch bewusst, dass das Erstellen dynamisch benannter Variablen in 99% der Zeit eine schlechte Idee ist und Wörterbücher der Weg sind, aber ich möchte nur wissen, ob es noch möglich ist und wie genau Exec und Locals in Python funktionieren 3.
Ich möchte ein bisschen Beispielcode zeigen, der meine Frage veranschaulicht (Fibonacci berechnet Fibonacci-Zahlen, ListOfLetters liefert ["A", "B", ...]):
%Vor% Zumindest nach meinem derzeitigen Verständnis gibt es einige Inkonsistenzen im Verhalten von locals()
, da es die Variablennamen enthält, die von exec()
hinzugefügt wurden, aber die Variablen sind in der Funktion nicht verfügbar.
Ich wäre dankbar, wenn jemand das erklären könnte und sagen könnte, ob das Absicht ist oder ob es eine echte Inkonsistenz in der Sprache ist. Ja, ich weiß, dass locals
nicht geändert werden sollte, aber ich ändere es nicht Ich rufe exec()
...
Wenn Sie nicht sicher sind, warum etwas so funktioniert wie in Python, kann es oft helfen, das von Ihnen verwirrte Verhalten in eine Funktion zu bringen und es dann mit dem dis
-Modul vom Python-Bytecode zu disassemblieren .
Beginnen wir mit einer einfacheren Version Ihres Codes:
%Vor% Wenn Sie foo()
ausführen, erhalten Sie dieselbe Ausnahme, die Sie bei Ihrer komplizierteren Funktion sehen:
Lasst es zerlegen und sehen warum:
%Vor% Die Operation, auf die Sie achten müssen, ist die mit "13". Dies ist der Punkt, an dem der Compiler in der letzten Zeile der Funktion K
nachschlägt ( print(K)
). Es benutzt den LOAD_GLOBAL
Opcode, was fehlschlägt, weil "K" kein globaler Variablenname ist, sondern ein Wert in unserem locals()
dict (hinzugefügt durch den exec
Aufruf).
Was ist, wenn wir den Compiler dazu verleitet haben, K
als lokale Variable zu sehen (indem wir ihm einen Wert geben, bevor er exec
ausführt), damit er nicht nach einer globalen Variablen sucht, die nicht existiert?
Diese Funktion gibt Ihnen keinen Fehler, wenn Sie sie ausführen, aber Sie erhalten nicht den erwarteten Wert ausgedruckt:
%Vor%Lasst uns zerlegen, um zu sehen, warum:
%Vor% Beachten Sie die bei "3" und "19" verwendeten Opcodes. Der Python-Compiler verwendet STORE_FAST
und LOAD_FAST
, um den Wert für die lokale Variable K
in Slot 0 zu übernehmen und ihn später wieder herauszuholen. Die Verwendung nummerierter Slots ist wesentlich schneller als das Einfügen und Abrufen von Werten aus einem Dictionary wie locals()
, weshalb der Python-Compiler dies für alle lokalen Variablenzugriffe in einer Funktion tut. Sie können eine lokale Variable in einem Slot nicht überschreiben, indem Sie das von locals()
zurückgegebene Dictionary ändern (wie exec
, wenn Sie kein dict übergeben, das für seinen Namespace verwendet werden soll).
In der Tat, versuchen wir eine dritte Version unserer Funktion, wo wir wieder in locals
hineinschauen, wenn wir K
als reguläre lokale Variable definiert haben:
Sie werden diesmal auch 89
in der Ausgabe nicht sehen!
Der Grund, warum Sie den alten Wert K
in locals()
sehen, wird in Dokumentation der Funktion
Aktualisieren und geben Sie ein Wörterbuch zurück, das die aktuelle lokale Symboltabelle darstellt.
Der Slot, in dem der Wert der lokalen Variablen K
gespeichert ist, wurde nicht von der Anweisung exec
geändert, die nur das locals()
dict ändert. Wenn Sie locals()
erneut aufrufen, aktualisiert Python [s] das Wörterbuch mit dem Wert aus dem Slot und ersetzt den dort gespeicherten Wert durch exec
.
Aus diesem Grund sagen die Docs weiter:
Hinweis: Der Inhalt dieses Wörterbuchs sollte nicht geändert werden. Änderungen dürfen die Werte der vom Interpreter verwendeten lokalen und freien Variablen nicht beeinflussen.
Ihr exec
Aufruf ändert das locals()
dict und Sie finden heraus, wie seine Änderungen nicht immer von Ihrem späteren Code erkannt werden.
Auf der Frage zu exec / eval / locals
Änderungen an der CPython-Implementierung Änderungen am locals()
-Wörterbuch ändern die Namen im lokalen Bereich nicht, weshalb sie nur als schreibgeschützt verwendet werden sollen. Sie können es ändern, und Sie können Ihre Änderungen im Wörterbuchobjekt sehen, aber der tatsächliche lokale Bereich wird nicht geändert.
exec()
benötigt zwei optionale Wörterbuchargumente, einen globalen Bereich und einen lokalen Bereich. Es ist standardmäßig globals()
und locals()
, aber da Änderungen an locals()
außerhalb des Wörterbuchs nicht "real" sind, beeinflusst exec()
nur den "echten" lokalen Bereich, wenn globals() is locals()
, dh in einem Modul außerhalb irgendeiner Funktion. (In Ihrem Fall funktioniert es nicht, weil es sich in einem Funktionsumfang befindet).
Der "bessere" Weg, exec()
in diesem Fall zu verwenden, besteht darin, ein eigenes Wörterbuch zu übergeben und dann die darin enthaltenen Werte zu bearbeiten.
In diesem Fall wird exec_scope
als globaler und lokaler Bereich für exec
verwendet und nach exec
enthält es {'y': 2, '__builtins__': __builtins__}
(die eingebauten Objekte werden für Sie eingefügt, wenn sie nicht vorhanden sind)
Wenn du Zugang zu mehr Globalen möchtest, kannst du exec_scope = dict(globals())
machen.
Durch die Übergabe verschiedener globaler und lokaler Wörterbücher kann ein "interessantes" Verhalten erzielt werden.
Wenn Sie dasselbe Wörterbuch in aufeinanderfolgenden Aufrufen an exec
oder eval
übergeben, haben sie denselben Gültigkeitsbereich, weshalb Ihr eval
funktioniert hat (implizit wurde das locals()
Wörterbuch verwendet).
Bei dynamischen Variablennamen
Wenn Sie den Namen aus einem String setzen, was ist falsch daran, den Wert als String zu erhalten (d. h. was für ein Wörterbuch)? Mit anderen Worten: Warum sollten Sie wollen locals()['K']
setzen und dann auf K
zugreifen? Wenn K
in Ihrer Quelle ist, ist es nicht wirklich ein dynamisch gesetzter Name ... daher Wörterbücher.
Tags und Links python python-3.x