Zugriff auf Klassenvariablen über ein Listenverständnis in der Klassendefinition

174

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.xfunktioniert 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.

Mark Lodato
quelle
Ihre Fehlermeldung ist falsch. Ich komme NameError: global name 'x' is not definedauf Python 3.2 und 3.3, was ich erwarten würde.
Martijn Pieters
Interessant ... Eine naheliegende Problemumgehung besteht darin, y zuzuweisen, nachdem Sie die Klassendefinition verlassen haben. Foo.y = [Foo.x für i in Bereich (1)]
GPS
3
+ martijn-pieters Link zu einem Duplikat ist richtig, es gibt einen Kommentar von + matt-b mit der Erklärung: Python 2.7-Listenverständnisse haben keinen eigenen Namespace (im Gegensatz zu Set- oder Dikt-Verständnissen oder Generatorausdrücken ... ersetzen Sie Ihre [ ] mit {}, um das in Aktion zu sehen). Sie alle haben ihren eigenen Namespace in 3.
GPS
@gps: Oder verwenden Sie einen verschachtelten Bereich, indem Sie eine (temporäre) Funktion in die Klassendefinitionssuite einfügen.
Martijn Pieters
Ich habe gerade am 2.7.11 getestet. Haben Sie Name Fehler
Junchao Gu

Antworten:

244

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 classDokumentation 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 xals verschachtelte Bereichsvariable bezeichnet wird, Foo.xbeispielsweise 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 xin 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 forKlauseln enthält, werden die Iterablen für innere forKlauseln 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 disModul 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_CONSTdort lädt ein Codeobjekt für den FooKlassenkö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 classSuite 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_constsStruktur:

>>> 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 xund ywird zum Erstellen der Klasse verwendet (außer dass sie nicht funktioniert, weil sie xnicht als global definiert ist). Beachten Sie, dass nach dem Speichern 5in 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_ITERSchleife 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 xes 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_ITERSchleife, um die Ausgabe zu erstellen.

Hätten wir stattdessen xin der fooFunktion definiert , xwä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_DEREFwird indirekt xaus 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 xVariable, 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' yFunktion 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 namedtuplein 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'),
       # ...
    ]]
Martijn Pieters
quelle
21
Sie können auch ein Lambda verwenden, um die Bindung zu reparieren:y = (lambda x=x: [x for i in range(1)])()
ecatmur
3
@ecatmur: Genau, schließlich lambdasind es nur anonyme Funktionen.
Martijn Pieters
2
Für den Datensatz hat die Problemumgehung, die ein Standardargument (an ein Lambda oder eine Funktion) zum Übergeben der Klassenvariablen verwendet, ein Gotcha. Es wird nämlich der aktuelle Wert der Variablen übergeben. Wenn sich die Variable später ändert und dann das Lambda oder die Funktion aufgerufen wird, verwendet das Lambda oder die Funktion den alten Wert. Dieses Verhalten unterscheidet sich vom Verhalten eines Abschlusses (der einen Verweis auf die Variable anstelle ihres Werts erfasst) und kann daher unerwartet sein.
Neal Young
9
Wenn eine Seite mit technischen Informationen erforderlich ist, um zu erklären, warum etwas nicht intuitiv funktioniert, nenne ich das einen Fehler.
Jonathan
5
@ JonathanLeaders: Nennen Sie es nicht einen Fehler , sondern einen Kompromiss . Wenn Sie A und B wollen, aber nur eines davon bekommen können, werden Sie das Ergebnis in einigen Situationen nicht mögen, egal wie Sie sich entscheiden. So ist das Leben.
Lutz Prechelt
15

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 definedin 3+):

class A:
    x = 4
    y = [x+i for i in range(1)]

HINWEIS: Ein einfaches Scoping mit A.xwürde es nicht lösen

New Way (funktioniert in 3+):

class A:
    x = 4
    y = (lambda x=x: [x+i for i in range(1)])()

Weil die Syntax so hässlich ist, initialisiere ich normalerweise alle meine Klassenvariablen im Konstruktor

Jonathan
quelle
6
Das Problem tritt auch in Python 2 auf, wenn Generatorausdrücke verwendet werden, sowie bei Set- und Wörterbuchverständnissen. Es ist kein Fehler, sondern eine Folge der Funktionsweise von Klassennamensräumen. Es wird sich nicht ändern.
Martijn Pieters
4
Und ich stelle fest, dass Ihre Problemumgehung genau das tut, was in meiner Antwort bereits angegeben ist: Erstellen eines neuen Bereichs (ein Lambda unterscheidet sich hier nicht von der Verwendung defzum Erstellen einer Funktion).
Martijn Pieters
1
ja. Während es schön ist, eine Antwort mit der Problemumgehung auf einen Blick zu haben, gibt diese das Verhalten fälschlicherweise als Fehler an, wenn es ein Nebeneffekt der Funktionsweise der Sprache ist (und daher nicht geändert wird).
Jsbueno
Dies ist ein anderes Problem, das in Python 3 eigentlich kein Problem darstellt. Es tritt in IPython nur auf, wenn Sie es im Einbettungsmodus mit say aufrufen python -c "import IPython;IPython.embed()". Führen Sie IPython direkt mit say aus, ipythonund das Problem wird behoben.
Riaz Rizvi
6

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:

class Foo:

    # A class-level variable.
    X = 10

    # I can use that variable to define another class-level variable.
    Y = sum((X, X))

    # Works in Python 2, but not 3.
    # In Python 3, list comprehensions were given their own scope.
    try:
        Z1 = sum([X for _ in range(3)])
    except NameError:
        Z1 = None

    # Fails in both.
    # Apparently, generator expressions (that's what the entire argument
    # to sum() is) did have their own scope even in Python 2.
    try:
        Z2 = sum(X for _ in range(3))
    except NameError:
        Z2 = None

    # Workaround: put the computation in lambda or def.
    compute_z3 = lambda val: sum(val for _ in range(3))

    # Then use that function.
    Z3 = compute_z3(X)

    # Also worth noting: here I can refer to XS in the for-part of the
    # generator expression (Z4 works), but I cannot refer to XS in the
    # inner-part of the generator expression (Z5 fails).
    XS = [15, 15, 15, 15]
    Z4 = sum(val for val in XS)
    try:
        Z5 = sum(XS[i] for i in range(len(XS)))
    except NameError:
        Z5 = None

print(Foo.Z1, Foo.Z2, Foo.Z3, Foo.Z4, Foo.Z5)
FMc
quelle
2

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:

class Foo:
    x = 5
    y = [x for i in range(1)]

Aber das funktioniert:

def Foo():
    x = 5
    y = [x for i in range(1)]

Die Einschränkung ist am Ende dieses Abschnitts im Referenzhandbuch angegeben.

bzip2
quelle
1

Da der äußerste Iterator im umgebenden Bereich ausgewertet wird, können wir zipzusammen mit verwenden itertools.repeat, um die Abhängigkeiten auf den Bereich des Verständnisses zu übertragen:

import itertools as it

class Foo:
    x = 5
    y = [j for i, j in zip(range(3), it.repeat(x))]

Man kann auch verschachtelte forSchleifen im Verständnis verwenden und die Abhängigkeiten in die äußerste iterable einschließen:

class Foo:
    x = 5
    y = [j for j in (x,) for i in range(3)]

Für das spezifische Beispiel des OP:

from collections import namedtuple
import itertools as it

class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for State, args in zip(it.repeat(State), [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ])]
ein Gast
quelle