Dynamisch benannte Variablen in einer Funktion in Python erstellen 3 / exec / eval / locals in Python verstehen 3

8

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() ...

an     
OBu 01.08.2014, 09:20
quelle

2 Antworten

12

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:

%Vor%

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?

%Vor%

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:

%Vor%

Sie werden diesmal auch 89 in der Ausgabe nicht sehen!

%Vor%

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.

    
Blckknght 08.08.2014, 07:13
quelle
5

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.

%Vor%

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.

    
Jason S 04.08.2014 06:27
quelle

Tags und Links