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 :
Auf Namen im Klassenbereich kann nicht zugegriffen werden. Namen werden im innersten umschließenden Funktionsbereich aufgelöst. Wenn eine Klassendefinition in einer Kette verschachtelter Bereiche auftritt, überspringt der Auflösungsprozess Klassendefinitionen.
und in der class
Dokumentation der zusammengesetzten Anweisung :
Die Suite der Klasse wird dann in einem neuen Ausführungsrahmen (siehe Abschnitt Benennung und Bindung ) unter Verwendung eines neu erstellten lokalen Namespace und des ursprünglichen globalen Namespace ausgeführt. (Normalerweise enthält die Suite nur Funktionsdefinitionen.) Wenn die Suite der Klasse die Ausführung beendet hat, wird ihr Ausführungsrahmen verworfen, aber ihr lokaler Namespace wird gespeichert . [4] Anschließend wird ein Klassenobjekt mithilfe der Vererbungsliste für die Basisklassen und des gespeicherten lokalen Namespace für das Attributwörterbuch erstellt.
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 bedeuten Foo
? 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:
Der Umfang der in einem Klassenblock definierten Namen ist auf den Klassenblock beschränkt. Es erstreckt sich nicht auf die Codeblöcke von Methoden - dies schließt Verständnisse und Generatorausdrücke ein, da sie unter Verwendung eines Funktionsumfangs implementiert werden. Dies bedeutet, dass Folgendes fehlschlägt:
class A:
a = 42
b = list(a + i for i in range(10))
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.
# Same error, in Python 2 or 3
y = {x: x for i in range(1)}
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)
:
y = [x for i in range(1)]
# ^^^^^^^^
Die Verwendung x
in diesem Ausdruck würde also keinen Fehler auslösen:
# Runs fine
y = [i for i in range(x)]
Dies gilt nur für die äußerste iterable; Wenn ein Verständnis mehrere for
Klauseln enthält, werden die Iterablen für innere for
Klauseln im Umfang des Verständnisses ausgewertet:
# NameError
y = [i for i in range(1) for j in range(x)]
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:
>>> import dis
>>> def foo():
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo)
2 0 LOAD_BUILD_CLASS
1 LOAD_CONST 1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>)
4 LOAD_CONST 2 ('Foo')
7 MAKE_FUNCTION 0
10 LOAD_CONST 2 ('Foo')
13 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
16 STORE_FAST 0 (Foo)
5 19 LOAD_FAST 0 (Foo)
22 RETURN_VALUE
Das erste LOAD_CONST
dort lädt ein Codeobjekt für den Foo
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:
>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
2 0 LOAD_FAST 0 (__locals__)
3 STORE_LOCALS
4 LOAD_NAME 0 (__name__)
7 STORE_NAME 1 (__module__)
10 LOAD_CONST 0 ('foo.<locals>.Foo')
13 STORE_NAME 2 (__qualname__)
3 16 LOAD_CONST 1 (5)
19 STORE_NAME 3 (x)
4 22 LOAD_CONST 2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>)
25 LOAD_CONST 3 ('foo.<locals>.Foo.<listcomp>')
28 MAKE_FUNCTION 0
31 LOAD_NAME 4 (range)
34 LOAD_CONST 4 (1)
37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
40 GET_ITER
41 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
44 STORE_NAME 5 (y)
47 LOAD_CONST 5 (None)
50 RETURN_VALUE
Der obige Bytecode erstellt den Klassenkörper. Die Funktion wird ausgeführt und der resultierende locals()
Namespace enthält x
und y
wird zum Erstellen der Klasse verwendet (außer dass sie nicht funktioniert, weil sie x
nicht als global definiert ist). Beachten Sie, dass nach dem Speichern 5
in x
, 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, das range(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:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (5)
9 STORE_NAME 2 (x)
4 12 BUILD_LIST 0
15 LOAD_NAME 3 (range)
18 LOAD_CONST 1 (1)
21 CALL_FUNCTION 1
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_NAME 4 (i)
31 LOAD_NAME 2 (x)
34 LIST_APPEND 2
37 JUMP_ABSOLUTE 25
>> 40 STORE_NAME 5 (y)
43 LOAD_LOCALS
44 RETURN_VALUE
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:
>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
4 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Dieser Teil des Bytecodes lädt das erste übergebene Argument (den range(1)
Iterator) und verwendet genau wie die Python 2.x-Version eine FOR_ITER
Schleife, um die Ausgabe zu erstellen.
Hätten wir stattdessen x
in der foo
Funktion definiert , x
wäre dies eine Zellvariable (Zellen beziehen sich auf verschachtelte Bereiche):
>>> def foo():
... x = 2
... class Foo:
... x = 5
... y = [x for i in range(1)]
... return Foo
...
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
5 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (x)
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
Das LOAD_DEREF
wird indirekt x
aus den Codeobjektzellenobjekten geladen :
>>> foo.__code__.co_cellvars # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars # Refers to `x` in foo
('x',)
>>> foo().y
[2]
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:
>>> def spam(x):
... def eggs():
... return x
... return eggs
...
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5
Um es zusammenzufassen:
- Listenverständnisse erhalten in Python 3 ihre eigenen Codeobjekte, und es gibt keinen Unterschied zwischen Codeobjekten für Funktionen, Generatoren oder Verständnisse. Verständniscode-Objekte werden in ein temporäres Funktionsobjekt eingeschlossen und sofort aufgerufen.
- Codeobjekte werden zur Kompilierungszeit erstellt, und alle nicht lokalen Variablen werden basierend auf den verschachtelten Bereichen des Codes entweder als globale oder als freie Variablen markiert. Der Klassenkörper wird nicht als Bereich zum Nachschlagen dieser Variablen angesehen.
- Bei der Ausführung des Codes muss Python nur die globalen Elemente oder den Abschluss des aktuell ausgeführten Objekts untersuchen. Da der Compiler den Klassenkörper nicht als Bereich aufgenommen hat, wird der Namespace für temporäre Funktionen nicht berücksichtigt.
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:
>>> class Foo:
... x = 5
... def y(x):
... return [x for i in range(1)]
... y = y(x)
...
>>> Foo.y
[5]
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ücksichtigt x
:
>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)
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:
def __init__(self):
self.y = [self.x for i in range(1)]
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:
from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])
class StateDatabase:
db = [State(*args) for args in [
('Alabama', 'Montgomery'),
('Alaska', 'Juneau'),
# ...
]]
NameError: global name 'x' is not defined
auf Python 3.2 und 3.3, was ich erwarten würde.