Wie greifen Sie über ein Listenverständnis innerhalb der Klassendefinition auf andere Klassenvariablen zu? Folgendes funktioniert in Python 2, schlägt jedoch in Python 3 fehl:
class Foo:
x = 5
y = [x for i in range(1)]
Python 3.2 gibt den Fehler aus:
NameError: global name 'x' is not defined
Der Versuch Foo.x
funktioniert auch nicht. Irgendwelche Ideen dazu in Python 3?
Ein etwas komplizierteres motivierendes Beispiel:
from collections import namedtuple
class StateDatabase:
State = namedtuple('State', ['name', 'capital'])
db = [State(*args) for args in [
['Alabama', 'Montgomery'],
['Alaska', 'Juneau'],
# ...
]]
In diesem Beispiel apply()
wäre dies eine anständige Problemumgehung gewesen, aber es wurde leider aus Python 3 entfernt.
python
python-3.x
scope
list-comprehension
python-internals
Mark Lodato
quelle
quelle
NameError: global name 'x' is not defined
auf Python 3.2 und 3.3, was ich erwarten würde.Antworten:
Klassenumfang und Listen-, Mengen- oder Wörterbuchverständnis sowie Generatorausdrücke vermischen sich nicht.
Das Warum; oder das offizielle Wort dazu
In Python 3 wurde dem Listenverständnis ein eigener Bereich (lokaler Namespace) zugewiesen , um zu verhindern, dass die lokalen Variablen in den umgebenden Bereich übergehen (siehe Python-Listenverständnis bindet Namen auch nach dem Verständnis neu. Stimmt das? ). Das ist großartig, wenn man ein solches Listenverständnis in einem Modul oder in einer Funktion verwendet, aber in Klassen ist das Scoping ein wenig seltsam .
Dies ist in S. 227 dokumentiert :
und in der
class
Dokumentation der zusammengesetzten Anweisung :Hervorhebung von mir; Der Ausführungsrahmen ist der temporäre Bereich.
Da der Bereich als Attribute für ein Klassenobjekt verwendet wird, führt die Verwendung als nicht lokaler Bereich auch zu undefiniertem Verhalten. Was würde passieren, wenn eine Klassenmethode, die
x
als verschachtelte Bereichsvariable bezeichnet wird,Foo.x
beispielsweise auch manipuliert ? Was würde das für Unterklassen von bedeutenFoo
? Python muss einen Klassenbereich anders behandeln, da er sich stark von einem Funktionsbereich unterscheidet.Last but not least werden im Abschnitt über verknüpfte Namen und Bindungen in der Dokumentation zum Ausführungsmodell die Klassenbereiche explizit erwähnt:
Zusammenfassend lässt sich sagen, dass Sie nicht über Funktionen, Listenverständnisse oder Generatorausdrücke, die in diesem Bereich enthalten sind, auf den Klassenbereich zugreifen können. Sie tun so, als ob dieser Geltungsbereich nicht existiert. In Python 2 wurden Listenverständnisse mithilfe einer Verknüpfung implementiert, in Python 3 erhielten sie jedoch einen eigenen Funktionsumfang (wie sie es schon immer hätten tun sollen), sodass Ihr Beispiel unterbrochen wurde. Andere Verständnistypen haben unabhängig von der Python-Version ihren eigenen Geltungsbereich, sodass ein ähnliches Beispiel mit einem Satz- oder Diktatverständnis in Python 2 nicht funktioniert.
Die (kleine) Ausnahme; oder, warum ein Teil kann noch Arbeit
Es gibt einen Teil eines Verständnisses oder Generatorausdrucks, der unabhängig von der Python-Version im umgebenden Bereich ausgeführt wird. Das wäre der Ausdruck für das äußerste iterable. In Ihrem Beispiel ist es das
range(1)
:Die Verwendung
x
in diesem Ausdruck würde also keinen Fehler auslösen:Dies gilt nur für die äußerste iterable; Wenn ein Verständnis mehrere
for
Klauseln enthält, werden die Iterablen für innerefor
Klauseln im Umfang des Verständnisses ausgewertet:Diese Entwurfsentscheidung wurde getroffen, um einen Fehler bei der Erstellung von Genexp anstelle der Iterationszeit auszulösen, wenn die äußerste Iterierbarkeit eines Generatorausdrucks einen Fehler auslöst oder wenn sich herausstellt, dass die äußerste Iterierbarkeit nicht iterierbar ist. Verständnis teilt dieses Verhalten aus Gründen der Konsistenz.
Blick unter die Haube; oder viel detaillierter als Sie jemals wollten
Sie können dies alles in Aktion mit dem
dis
Modul sehen . In den folgenden Beispielen verwende ich Python 3.3, weil es qualifizierte Namen hinzufügt , die die Codeobjekte, die wir untersuchen möchten, genau identifizieren. Der erzeugte Bytecode ist ansonsten funktional identisch mit Python 3.2.Um eine Klasse zu erstellen , verwendet Python im Wesentlichen die gesamte Suite, aus der der Klassenkörper besteht (also wird alles eine Ebene tiefer als die
class <name>:
Zeile eingerückt ), und führt dies aus, als wäre es eine Funktion:Das erste
LOAD_CONST
dort lädt ein Codeobjekt für denFoo
Klassenkörper, macht es dann zu einer Funktion und ruft es auf. Das Ergebnis dieses Aufrufs wird dann verwendet, um den Namespace der Klasse its zu erstellen__dict__
. So weit, ist es gut.Hierbei ist zu beachten, dass der Bytecode ein verschachteltes Codeobjekt enthält. In Python werden Klassendefinitionen, Funktionen, Verständnis und Generatoren als Codeobjekte dargestellt, die nicht nur Bytecode enthalten, sondern auch Strukturen, die lokale Variablen, Konstanten, Variablen aus Globals und Variablen aus dem verschachtelten Bereich darstellen. Der kompilierte Bytecode bezieht sich auf diese Strukturen, und der Python-Interpreter weiß, wie er mit den angegebenen Bytecodes auf diese zugreifen kann.
Das Wichtigste dabei ist, dass Python diese Strukturen zur Kompilierungszeit erstellt. Die
class
Suite ist ein Codeobjekt (<code object Foo at 0x10a436030, file "<stdin>", line 2>
), das bereits kompiliert wurde.Lassen Sie uns das Codeobjekt untersuchen, das den Klassenkörper selbst erstellt. Codeobjekte haben eine
co_consts
Struktur:Der obige Bytecode erstellt den Klassenkörper. Die Funktion wird ausgeführt und der resultierende
locals()
Namespace enthältx
undy
wird zum Erstellen der Klasse verwendet (außer dass sie nicht funktioniert, weil siex
nicht als global definiert ist). Beachten Sie, dass nach dem Speichern5
inx
, es mit einem anderen Code - Objekt lädt; das ist das Listenverständnis; Es ist genau wie der Klassenkörper in ein Funktionsobjekt eingeschlossen. Die erstellte Funktion verwendet ein Positionsargument, dasrange(1)
für ihren Schleifencode iterierbar ist und in einen Iterator umgewandelt wird. Wie im Bytecode gezeigt,range(1)
wird im Klassenbereich ausgewertet.Daraus können Sie ersehen, dass der einzige Unterschied zwischen einem Codeobjekt für eine Funktion oder einen Generator und einem Codeobjekt für ein Verständnis darin besteht, dass letzteres sofort ausgeführt wird, wenn das übergeordnete Codeobjekt ausgeführt wird. Der Bytecode erstellt einfach eine Funktion im laufenden Betrieb und führt sie in wenigen kleinen Schritten aus.
Python 2.x verwendet dort stattdessen Inline-Bytecode. Hier wird Python 2.7 ausgegeben:
Es wird kein Codeobjekt geladen, stattdessen wird eine
FOR_ITER
Schleife inline ausgeführt. In Python 3.x erhielt der Listengenerator ein eigenes Codeobjekt, was bedeutet, dass er einen eigenen Bereich hat.Das Verständnis wurde jedoch zusammen mit dem Rest des Python-Quellcodes kompiliert, als das Modul oder Skript zum ersten Mal vom Interpreter geladen wurde, und der Compiler betrachtet eine Klassensuite nicht als gültigen Bereich. Irgendwelche referenzierten Variablen in einer Liste Verständnis muss im Rahmen aussehen umgibt die Klassendefinition, rekursiv. Wenn die Variable vom Compiler nicht gefunden wurde, wird sie als global markiert. Die Zerlegung des Listenverständnis-Codeobjekts zeigt, dass
x
es tatsächlich als globales Objekt geladen ist:Dieser Teil des Bytecodes lädt das erste übergebene Argument (den
range(1)
Iterator) und verwendet genau wie die Python 2.x-Version eineFOR_ITER
Schleife, um die Ausgabe zu erstellen.Hätten wir stattdessen
x
in derfoo
Funktion definiert ,x
wäre dies eine Zellvariable (Zellen beziehen sich auf verschachtelte Bereiche):Das
LOAD_DEREF
wird indirektx
aus den Codeobjektzellenobjekten geladen :Bei der eigentlichen Referenzierung wird der Wert aus den aktuellen Rahmendatenstrukturen nachgeschlagen, die aus dem
.__closure__
Attribut eines Funktionsobjekts initialisiert wurden . Da die für das Verständniscode-Objekt erstellte Funktion erneut verworfen wird, können wir den Abschluss dieser Funktion nicht überprüfen. Um einen Abschluss in Aktion zu sehen, müssten wir stattdessen eine verschachtelte Funktion untersuchen:Um es zusammenzufassen:
Eine Problemumgehung; oder was dagegen zu tun ist
Wenn Sie sind einen expliziten Spielraum für die schaffen
x
Variable, wie in einer Funktion, Sie können Klasse-scope Variablen für eine Liste Verständnis verwenden:Die 'temporäre'
y
Funktion kann direkt aufgerufen werden; Wir ersetzen es, wenn wir es mit seinem Rückgabewert tun. Sein Umfang wird bei der Lösung berücksichtigtx
:Natürlich kratzen sich die Leute, die Ihren Code lesen, ein wenig am Kopf. Vielleicht möchten Sie dort einen großen, fetten Kommentar einfügen, der erklärt, warum Sie dies tun.
Die beste
__init__
Lösung besteht darin, stattdessen nur eine Instanzvariable zu erstellen:und vermeiden Sie all das Kopfkratzen und Fragen, um sich selbst zu erklären. Für Ihr eigenes konkretes Beispiel würde ich das nicht einmal
namedtuple
in der Klasse speichern ; Verwenden Sie entweder die Ausgabe direkt (speichern Sie die generierte Klasse überhaupt nicht) oder verwenden Sie eine globale:quelle
y = (lambda x=x: [x for i in range(1)])()
lambda
sind es nur anonyme Funktionen.Meiner Meinung nach ist es ein Fehler in Python 3. Ich hoffe, sie ändern ihn.
Old Way (funktioniert in 2.7, wirft
NameError: name 'x' is not defined
in 3+):HINWEIS: Ein einfaches Scoping mit
A.x
würde es nicht lösenNew Way (funktioniert in 3+):
Weil die Syntax so hässlich ist, initialisiere ich normalerweise alle meine Klassenvariablen im Konstruktor
quelle
def
zum Erstellen einer Funktion).python -c "import IPython;IPython.embed()"
. Führen Sie IPython direkt mit say aus,ipython
und das Problem wird behoben.Die akzeptierte Antwort liefert ausgezeichnete Informationen, aber es scheint hier einige andere Falten zu geben - Unterschiede zwischen Listenverständnis und Generatorausdrücken. Eine Demo, mit der ich herumgespielt habe:
quelle
Dies ist ein Fehler in Python. Verständnis wird als äquivalent zu for-Schleifen beworben, dies gilt jedoch nicht für Klassen. Zumindest bis Python 3.6.6 ist in einem in einer Klasse verwendeten Verständnis nur eine Variable von außerhalb des Verständnisses innerhalb des Verständnisses zugänglich und muss als äußerster Iterator verwendet werden. In einer Funktion gilt diese Bereichsbeschränkung nicht.
Um zu veranschaulichen, warum dies ein Fehler ist, kehren wir zum ursprünglichen Beispiel zurück. Dies schlägt fehl:
Aber das funktioniert:
Die Einschränkung ist am Ende dieses Abschnitts im Referenzhandbuch angegeben.
quelle
Da der äußerste Iterator im umgebenden Bereich ausgewertet wird, können wir
zip
zusammen mit verwendenitertools.repeat
, um die Abhängigkeiten auf den Bereich des Verständnisses zu übertragen:Man kann auch verschachtelte
for
Schleifen im Verständnis verwenden und die Abhängigkeiten in die äußerste iterable einschließen:Für das spezifische Beispiel des OP:
quelle