dataclasses — Data Classes

Source code: Lib/dataclasses.py


Dieses Modul stellt einen Dekorator und Funktionen zur Verfügung, um automatisch generierte Spezialmethoden wie __init__() und __repr__() für benutzerdefinierte Klassen hinzuzufügen. Es wurde ursprünglich in PEP 557 beschrieben.

Die Mitgliedsvariablen, die in diesen generierten Methoden verwendet werden sollen, werden mit PEP 526 Typannotationen definiert. Zum Beispiel, dieser Code

from dataclasses import dataclass

@dataclass
class InventoryItem:
    """Class for keeping track of an item in inventory."""
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

fügt unter anderem eine __init__() hinzu, die wie folgt aussieht:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
    self.name = name
    self.unit_price = unit_price
    self.quantity_on_hand = quantity_on_hand

Beachten Sie, dass diese Methode automatisch zur Klasse hinzugefügt wird: sie ist nicht direkt in der oben gezeigten InventoryItem Definition angegeben.

Hinzugefügt in Version 3.7.

Modulinhalt

@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)

Diese Funktion ist ein Dekorator, der verwendet wird, um generierte Spezialmethoden zu Klassen hinzuzufügen, wie unten beschrieben.

Der Dekorator @dataclass untersucht die Klasse auf fields. Ein field wird als Klassenvariable definiert, die eine Typannotation hat. Mit zwei Ausnahmen, die unten beschrieben werden, untersucht @dataclass nicht den Typ, der in der Variablenannotation angegeben ist.

Die Reihenfolge der Felder in allen generierten Methoden ist die Reihenfolge, in der sie in der Klassendefinition erscheinen.

Der Dekorator @dataclass fügt der Klasse verschiedene "dunder"-Methoden hinzu, die unten beschrieben werden. Wenn eine der hinzugefügten Methoden bereits in der Klasse existiert, hängt das Verhalten vom Parameter ab, wie unten dokumentiert. Der Dekorator gibt dieselbe Klasse zurück, auf die er angewendet wird; es wird keine neue Klasse erstellt.

Wenn @dataclass einfach als Dekorator ohne Parameter verwendet wird, verhält er sich so, als ob er die Standardwerte hätte, die in dieser Signatur dokumentiert sind. Das heißt, diese drei Verwendungen von @dataclass sind äquivalent:

@dataclass
class C:
    ...

@dataclass()
class C:
    ...

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False,
           match_args=True, kw_only=False, slots=False, weakref_slot=False)
class C:
    ...

Die Parameter für @dataclass sind:

  • init: Wenn wahr (Standardwert), wird eine __init__() Methode generiert.

    Wenn die Klasse bereits __init__() definiert, wird dieser Parameter ignoriert.

  • repr: Wenn wahr (Standardwert), wird eine __repr__() Methode generiert. Der generierte Repr-String enthält den Klassennamen und den Namen und das Repr jedes Feldes, in der Reihenfolge, in der sie in der Klasse definiert sind. Felder, die vom Repr ausgeschlossen sind, werden nicht einbezogen. Zum Beispiel: InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10).

    Wenn die Klasse bereits __repr__() definiert, wird dieser Parameter ignoriert.

  • eq: Wenn wahr (Standardwert), wird eine __eq__() Methode generiert. Diese Methode vergleicht die Klasse so, als ob sie ein Tupel ihrer Felder in Reihenfolge wäre. Beide Instanzen im Vergleich müssen vom identischen Typ sein.

    Wenn die Klasse bereits __eq__() definiert, wird dieser Parameter ignoriert.

  • order: Wenn wahr (Standardwert ist False), werden die Methoden __lt__(), __le__(), __gt__() und __ge__() generiert. Diese vergleichen die Klasse so, als ob sie ein Tupel ihrer Felder in Reihenfolge wäre. Beide Instanzen im Vergleich müssen vom identischen Typ sein. Wenn order wahr und eq falsch ist, wird ein ValueError ausgelöst.

    Wenn die Klasse bereits eine der Methoden __lt__(), __le__(), __gt__() oder __ge__() definiert, wird ein TypeError ausgelöst.

  • unsafe_hash: Wenn wahr, erzwingt dataclasses die Erstellung einer __hash__() Methode, auch wenn dies nicht sicher ist. Andernfalls wird eine __hash__() Methode basierend darauf generiert, wie eq und frozen gesetzt sind. Der Standardwert ist False.

    __hash__() wird von der integrierten Funktion hash() verwendet und wenn Objekte zu gehashten Sammlungen wie Wörterbüchern und Mengen hinzugefügt werden. Das Vorhandensein einer __hash__() impliziert, dass Instanzen der Klasse unveränderlich sind. Veränderlichkeit ist eine komplizierte Eigenschaft, die von der Absicht des Programmierers, der Existenz und dem Verhalten von __eq__() und den Werten der Flags eq und frozen im @dataclass Dekorator abhängt.

    Standardmäßig fügt @dataclass nicht implizit eine __hash__() Methode hinzu, es sei denn, dies ist sicher. Sie fügt auch keine bestehende, explizit definierte __hash__() Methode hinzu oder ändert sie. Das Setzen des Klassenattributs __hash__ = None hat eine spezielle Bedeutung für Python, wie in der Dokumentation zu __hash__() beschrieben.

    Wenn __hash__() nicht explizit definiert ist oder wenn es auf None gesetzt ist, *kann* @dataclass eine implizite __hash__() Methode hinzufügen. Obwohl nicht empfohlen, können Sie @dataclass mit unsafe_hash=True zwingen, eine __hash__() Methode zu erstellen. Dies kann der Fall sein, wenn Ihre Klasse logisch unveränderlich ist, aber dennoch verändert werden kann. Dies ist ein spezialisierter Anwendungsfall und sollte sorgfältig bedacht werden.

    Hier sind die Regeln für die implizite Erstellung einer __hash__() Methode. Beachten Sie, dass Sie weder eine explizite __hash__() Methode in Ihrer Dataclass haben noch unsafe_hash=True setzen können; dies führt zu einem TypeError.

    Wenn eq und frozen beide wahr sind, generiert @dataclass standardmäßig eine __hash__() Methode für Sie. Wenn eq wahr und frozen falsch ist, wird __hash__() auf None gesetzt, wodurch sie nicht hashbar markiert wird (was sie ist, da sie veränderlich ist). Wenn eq falsch ist, wird __hash__() unberührt gelassen, was bedeutet, dass die __hash__() Methode der Oberklasse verwendet wird (wenn die Oberklasse object ist, bedeutet dies, dass sie auf ID-basiertes Hashing zurückfällt).

  • frozen: Wenn wahr (Standardwert ist False), löst die Zuweisung an Felder eine Ausnahme aus. Dies emuliert schreibgeschützte, unveränderliche Instanzen. Siehe die Diskussion unten.

    Wenn __setattr__() oder __delattr__() in der Klasse definiert ist und frozen wahr ist, wird ein TypeError ausgelöst.

  • match_args: Wenn wahr (Standardwert ist True), wird das Tupel __match_args__ aus der Liste der nicht-keyword-only Parameter für die generierte __init__() Methode erstellt (auch wenn __init__() nicht generiert wird, siehe oben). Wenn falsch, oder wenn __match_args__ bereits in der Klasse definiert ist, wird __match_args__ nicht generiert.

Hinzugefügt in Version 3.10.

  • kw_only: Wenn wahr (Standardwert ist False), werden alle Felder als Keyword-only gekennzeichnet. Wenn ein Feld als Keyword-only gekennzeichnet ist, ist die einzige Auswirkung, dass der __init__() Parameter, der aus einem Keyword-only Feld generiert wird, bei der Verwendung von __init__() mit einem Schlüsselwort angegeben werden muss. Siehe den Parameter-Glossareintrag für Details. Siehe auch den Abschnitt KW_ONLY.

    Keyword-only Felder werden nicht in __match_args__ aufgenommen.

Hinzugefügt in Version 3.10.

  • slots: Wenn wahr (Standardwert ist False), wird das Attribut __slots__ generiert und eine neue Klasse wird anstelle der ursprünglichen zurückgegeben. Wenn __slots__ bereits in der Klasse definiert ist, wird ein TypeError ausgelöst.

Warnung

Die Übergabe von Parametern an die __init_subclass__() einer Basisklasse bei Verwendung von slots=True führt zu einem TypeError. Verwenden Sie entweder __init_subclass__ ohne Parameter oder verwenden Sie Standardwerte als Workaround. Siehe gh-91126 für vollständige Details.

Hinzugefügt in Version 3.10.

Geändert in Version 3.11: Wenn ein Feldname bereits in den __slots__ einer Basisklasse enthalten ist, wird er nicht in den generierten __slots__ enthalten sein, um deren Überschreibung zu verhindern. Verwenden Sie daher __slots__ nicht, um die Feldnamen einer Dataclass abzurufen. Verwenden Sie stattdessen fields(). Um vererbte Slots ermitteln zu können, können die __slots__ der Basisklasse jede iterierbare sein, aber *kein* Iterator.

  • weakref_slot: Wenn wahr (Standardwert ist False), wird ein Slot namens „__weakref__“ hinzugefügt, der erforderlich ist, um eine Instanz weakref-fähig zu machen. Es ist ein Fehler, weakref_slot=True anzugeben, ohne auch slots=True anzugeben.

Hinzugefügt in Version 3.11.

fields können optional einen Standardwert angeben, mit normaler Python-Syntax.

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

In diesem Beispiel werden sowohl a als auch b in die hinzugefügte __init__() Methode aufgenommen, die wie folgt definiert wird:

def __init__(self, a: int, b: int = 0):

Ein TypeError wird ausgelöst, wenn ein Feld ohne Standardwert einem Feld mit einem Standardwert folgt. Dies gilt sowohl, wenn dies in einer einzelnen Klasse geschieht, als auch als Ergebnis der Klassenerbschaft.

dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING, doc=None)

Für gängige und einfache Anwendungsfälle ist keine weitere Funktionalität erforderlich. Es gibt jedoch einige Dataclass-Features, die zusätzliche feldbezogene Informationen erfordern. Um diesen Bedarf an zusätzlichen Informationen zu decken, können Sie den Standardwert des Feldes durch einen Aufruf der bereitgestellten Funktion field() ersetzen. Zum Beispiel:

@dataclass
class C:
    mylist: list[int] = field(default_factory=list)

c = C()
c.mylist += [1, 2, 3]

Wie oben gezeigt, ist der Wert MISSING ein Sentinel-Objekt, das verwendet wird, um zu erkennen, ob einige Parameter vom Benutzer bereitgestellt werden. Dieses Sentinel wird verwendet, weil None ein gültiger Wert für einige Parameter mit einer eindeutigen Bedeutung ist. Kein Code sollte den Wert MISSING direkt verwenden.

Die Parameter für field() sind:

  • default: Wenn angegeben, ist dies der Standardwert für dieses Feld. Dies ist erforderlich, da der field() Aufruf selbst die normale Position des Standardwerts ersetzt.

  • default_factory: Wenn angegeben, muss es ein nullargumenten-aufrufbarer sein, der aufgerufen wird, wenn ein Standardwert für dieses Feld benötigt wird. Unter anderem kann dies verwendet werden, um Felder mit veränderlichen Standardwerten anzugeben, wie unten diskutiert. Es ist ein Fehler, sowohl default als auch default_factory anzugeben.

  • init: Wenn wahr (Standardwert), wird dieses Feld als Parameter für die generierte __init__() Methode aufgenommen.

  • repr: Wenn wahr (Standardwert), wird dieses Feld in den von der generierten __repr__() Methode zurückgegebenen String aufgenommen.

  • hash: Dies kann ein Boolesch oder None sein. Wenn wahr, wird dieses Feld in die generierte __hash__() Methode aufgenommen. Wenn falsch, wird dieses Feld aus der generierten __hash__() Methode ausgeschlossen. Wenn None (Standardwert), wird der Wert von compare verwendet: dies wäre normalerweise das erwartete Verhalten, da ein Feld in den Hash aufgenommen werden sollte, wenn es für Vergleiche verwendet wird. Das Setzen dieses Wertes auf etwas anderes als None wird nicht empfohlen.

    Ein möglicher Grund, hash=False, aber compare=True zu setzen, wäre, wenn ein Feld teuer zu berechnen ist, dieses Feld für die Gleichheitsprüfung benötigt wird und es andere Felder gibt, die zum Hash-Wert des Typs beitragen. Selbst wenn ein Feld vom Hash ausgeschlossen wird, wird es immer noch für Vergleiche verwendet.

  • compare: Wenn wahr (Standardwert), wird dieses Feld in die generierten Gleichheits- und Vergleichsmethoden (__eq__(), __gt__(), et al.) aufgenommen.

  • metadata: Dies kann eine Zuordnung oder None sein. None wird als leeres Dict behandelt. Dieser Wert wird in MappingProxyType() verpackt, um ihn schreibgeschützt zu machen, und im Field-Objekt exponiert. Er wird von Data Classes überhaupt nicht verwendet und dient als Erweiterungsmechanismus für Dritte. Mehrere Dritte können jeweils ihren eigenen Schlüssel haben, um als Namespace in den Metadaten zu fungieren.

  • kw_only: Wenn wahr, wird dieses Feld als Keyword-only gekennzeichnet. Dies wird verwendet, wenn die Parameter der generierten __init__() Methode berechnet werden.

    Keyword-only Felder werden auch nicht in __match_args__ aufgenommen.

Hinzugefügt in Version 3.10.

  • doc: Optionaler Docstring für dieses Feld.

Hinzugefügt in Version 3.14.

Wenn der Standardwert eines Feldes durch einen Aufruf von field() angegeben wird, wird das Klassenattribut für dieses Feld durch den angegebenen Wert default ersetzt. Wenn default nicht angegeben ist, wird das Klassenattribut gelöscht. Die Absicht ist, dass nach Ausführung des Dekorators @dataclass die Klassenattribute alle Standardwerte für die Felder enthalten, so als ob der Standardwert selbst angegeben wäre. Zum Beispiel, nach

@dataclass
class C:
    x: int
    y: int = field(repr=False)
    z: int = field(repr=False, default=10)
    t: int = 20

Das Klassenattribut C.z wird 10 sein, das Klassenattribut C.t wird 20 sein, und die Klassenattribute C.x und C.y werden nicht gesetzt.

class dataclasses.Field

Field Objekte beschreiben jedes definierte Feld. Diese Objekte werden intern erstellt und von der Modulmethode fields() zurückgegeben (siehe unten). Benutzer sollten niemals ein Field Objekt direkt instanziieren. Seine dokumentierten Attribute sind:

  • name: Der Name des Feldes.

  • type: Der Typ des Feldes.

  • default, default_factory, init, repr, hash, compare, metadata und kw_only haben die gleiche Bedeutung und Werte wie in der Funktion field().

Andere Attribute können existieren, sind aber privat und dürfen nicht inspiziert oder darauf vertraut werden.

class dataclasses.InitVar

InitVar[T] Typannotationen beschreiben Variablen, die nur für die Initialisierung bestimmt sind. Felder, die mit InitVar annotiert sind, werden als Pseudo-Felder betrachtet und daher weder von der Funktion fields() zurückgegeben noch auf irgendeine Weise verwendet, außer dass sie als Parameter zu __init__() und einer optionalen __post_init__() hinzugefügt werden.

dataclasses.fields(class_or_instance)

Gibt ein Tupel von Field Objekten zurück, die die Felder für diese Dataclass definieren. Akzeptiert entweder eine Dataclass oder eine Instanz einer Dataclass. Löst TypeError aus, wenn keine Dataclass oder Instanz davon übergeben wird. Gibt keine Pseudo-Felder zurück, die ClassVar oder InitVar sind.

dataclasses.asdict(obj, *, dict_factory=dict)

Konvertiert die Dataclass obj in ein Dict (unter Verwendung der Factory-Funktion dict_factory). Jede Dataclass wird in ein Dict ihrer Felder konvertiert, als name: value Paare. Dataclasses, Dicts, Listen und Tupel werden rekursiv durchlaufen. Andere Objekte werden mit copy.deepcopy() kopiert.

Beispiel für die Verwendung von asdict() auf verschachtelten Dataclasses

@dataclass
class Point:
     x: int
     y: int

@dataclass
class C:
     mylist: list[Point]

p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}

c = C([Point(0, 0), Point(10, 4)])
assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}

Um eine flache Kopie zu erstellen, kann die folgende Umgehung verwendet werden:

{field.name: getattr(obj, field.name) for field in fields(obj)}

asdict() löst TypeError aus, wenn obj keine Instanz einer Dataclass ist.

dataclasses.astuple(obj, *, tuple_factory=tuple)

Konvertiert die Dataclass obj in ein Tupel (mithilfe der Factory-Funktion tuple_factory). Jede Dataclass wird in ein Tupel ihrer Feldwerte konvertiert. Dataclasses, Dictionaries, Listen und Tupel werden rekursiv verarbeitet. Andere Objekte werden mit copy.deepcopy() kopiert.

Fortsetzung des vorherigen Beispiels

assert astuple(p) == (10, 20)
assert astuple(c) == ([(0, 0), (10, 4)],)

Um eine flache Kopie zu erstellen, kann die folgende Umgehung verwendet werden:

tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))

astuple() löst TypeError aus, wenn obj keine Instanz einer Dataclass ist.

dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None, decorator=dataclass)

Erstellt eine neue Dataclass mit dem Namen cls_name, Feldern wie in fields definiert, Basisklassen wie in bases angegeben und initialisiert mit einem Namespace wie in namespace angegeben. fields ist ein Iterable, dessen Elemente jeweils entweder name, (name, type) oder (name, type, Field) sind. Wenn nur name angegeben wird, wird typing.Any für type verwendet. Die Werte von init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots und weakref_slot haben die gleiche Bedeutung wie in @dataclass.

Wenn module definiert ist, wird das Attribut __module__ der Dataclass auf diesen Wert gesetzt. Standardmäßig wird es auf den Modulnamen des Aufrufers gesetzt.

Der Parameter decorator ist eine aufrufbare Funktion, die zur Erstellung der Dataclass verwendet wird. Sie sollte die Klasse als erstes Argument und die gleichen Schlüsselwortargumente wie @dataclass erhalten. Standardmäßig wird die Funktion @dataclass verwendet.

Diese Funktion ist nicht unbedingt erforderlich, da jeder Python-Mechanismus zur Erstellung einer neuen Klasse mit __annotations__ anschließend die Funktion @dataclass anwenden kann, um diese Klasse in eine Dataclass zu konvertieren. Diese Funktion wird als Bequemlichkeit bereitgestellt. Zum Beispiel

C = make_dataclass('C',
                   [('x', int),
                     'y',
                    ('z', int, field(default=5))],
                   namespace={'add_one': lambda self: self.x + 1})

Ist äquivalent zu

@dataclass
class C:
    x: int
    y: 'typing.Any'
    z: int = 5

    def add_one(self):
        return self.x + 1

Hinzugefügt in Version 3.14: Der Parameter decorator wurde hinzugefügt.

dataclasses.replace(obj, /, **changes)

Erstellt ein neues Objekt desselben Typs wie obj und ersetzt Felder durch Werte aus changes. Wenn obj keine Data Class ist, wird TypeError ausgelöst. Wenn Schlüssel in changes keine Feldnamen der gegebenen Dataclass sind, wird TypeError ausgelöst.

Das neu zurückgegebene Objekt wird durch Aufruf der Methode __init__() der Dataclass erstellt. Dies stellt sicher, dass auch __post_init__(), falls vorhanden, aufgerufen wird.

Init-only Variablen ohne Standardwerte müssen, falls vorhanden, beim Aufruf von replace() angegeben werden, damit sie an __init__() und __post_init__() übergeben werden können.

Es ist ein Fehler, wenn changes Felder enthält, die als init=False definiert sind. In diesem Fall wird ein ValueError ausgelöst.

Seien Sie sich bewusst, wie Felder mit init=False während eines Aufrufs von replace() funktionieren. Sie werden nicht vom Quellobjekt kopiert, sondern werden in __post_init__() initialisiert, falls sie überhaupt initialisiert werden. Es wird erwartet, dass Felder mit init=False selten und mit Bedacht verwendet werden. Wenn sie verwendet werden, kann es ratsam sein, alternative Klassenkonstruktoren oder vielleicht eine benutzerdefinierte Methode replace() (oder ähnlich benannt) zu haben, die die Instanzkopierung handhabt.

Dataclass-Instanzen werden auch von der generischen Funktion copy.replace() unterstützt.

dataclasses.is_dataclass(obj)

Gibt True zurück, wenn der Parameter eine Dataclass (einschließlich Unterklassen einer Dataclass) oder eine Instanz davon ist, andernfalls gibt es False zurück.

Wenn Sie wissen müssen, ob eine Klasse eine Instanz einer Dataclass ist (und nicht selbst eine Dataclass), fügen Sie eine weitere Prüfung für not isinstance(obj, type) hinzu.

def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)
dataclasses.MISSING

Ein Sentinel-Wert, der einen fehlenden Standardwert oder eine fehlende Standard-Factory anzeigt.

dataclasses.KW_ONLY

Ein Sentinel-Wert, der als Typannotation verwendet wird. Alle Felder nach einem Pseudo-Feld mit dem Typ KW_ONLY werden als Schlüsselwort-only Felder markiert. Beachten Sie, dass ein Pseudo-Feld vom Typ KW_ONLY ansonsten völlig ignoriert wird. Dies gilt auch für den Namen eines solchen Feldes. Konventionsgemäß wird der Name _ für ein Feld vom Typ KW_ONLY verwendet. Schlüsselwort-only Felder kennzeichnen __init__()-Parameter, die bei der Instanziierung der Klasse als Schlüsselwörter angegeben werden müssen.

In diesem Beispiel werden die Felder y und z als Schlüsselwort-only Felder markiert.

@dataclass
class Point:
    x: float
    _: KW_ONLY
    y: float
    z: float

p = Point(0, y=1.5, z=2.0)

In einer einzelnen Dataclass ist es ein Fehler, mehr als ein Feld anzugeben, dessen Typ KW_ONLY ist.

Hinzugefügt in Version 3.10.

exception dataclasses.FrozenInstanceError

Ausgelöst, wenn eine implizit definierte Methode __setattr__() oder __delattr__() auf einer Dataclass aufgerufen wird, die mit frozen=True definiert wurde. Sie ist eine Unterklasse von AttributeError.

Nachinitialisierungsverarbeitung

dataclasses.__post_init__()

Wenn sie auf der Klasse definiert ist, wird sie von der generierten Methode __init__() aufgerufen, normalerweise als self.__post_init__(). Wenn jedoch InitVar-Felder definiert sind, werden diese ebenfalls an __post_init__() übergeben, in der Reihenfolge, in der sie in der Klasse definiert wurden. Wenn keine Methode __init__() generiert wird, wird __post_init__() nicht automatisch aufgerufen.

Unter anderem ermöglicht dies die Initialisierung von Feldwerten, die von einem oder mehreren anderen Feldern abhängen. Zum Beispiel

@dataclass
class C:
    a: float
    b: float
    c: float = field(init=False)

    def __post_init__(self):
        self.c = self.a + self.b

Die von @dataclass generierte Methode __init__() ruft keine __init__()-Methoden der Basisklasse auf. Wenn die Basisklasse eine Methode __init__() hat, die aufgerufen werden muss, ist es üblich, diese Methode in einer Methode __post_init__() aufzurufen.

class Rectangle:
    def __init__(self, height, width):
        self.height = height
        self.width = width

@dataclass
class Square(Rectangle):
    side: float

    def __post_init__(self):
        super().__init__(self.side, self.side)

Beachten Sie jedoch, dass im Allgemeinen die von Dataclass generierten Methoden __init__() nicht aufgerufen werden müssen, da die abgeleitete Dataclass die Initialisierung aller Felder einer Basisklasse übernimmt, die selbst eine Dataclass ist.

Siehe den Abschnitt über Init-only Variablen für Möglichkeiten, Parameter an __post_init__() zu übergeben. Sehen Sie auch die Warnung, wie replace() Felder mit init=False behandelt.

Klassenvariablen

Einer der wenigen Fälle, in denen @dataclass den Typ eines Feldes tatsächlich inspiziert, ist die Bestimmung, ob ein Feld eine Klassenvariable gemäß PEP 526 ist. Dies geschieht durch Überprüfung, ob der Typ des Feldes typing.ClassVar ist. Wenn ein Feld eine ClassVar ist, wird es nicht als Feld betrachtet und von den Dataclass-Mechanismen ignoriert. Solche ClassVar-Pseudo-Felder werden nicht von der Modul-Funktion fields() zurückgegeben.

Init-only Variablen

Ein weiterer Fall, in dem @dataclass eine Typannotation inspiziert, ist die Bestimmung, ob ein Feld eine Init-only Variable ist. Dies geschieht durch Überprüfung, ob der Typ eines Feldes vom Typ InitVar ist. Wenn ein Feld eine InitVar ist, wird es als Pseudo-Feld namens Init-only Feld betrachtet. Da es sich nicht um ein echtes Feld handelt, wird es nicht von der Modul-Funktion fields() zurückgegeben. Init-only Felder werden als Parameter zur generierten Methode __init__() hinzugefügt und an die optionale Methode __post_init__() übergeben. Sie werden von Dataclasses sonst nicht verwendet.

Zum Beispiel, angenommen ein Feld wird aus einer Datenbank initialisiert, wenn beim Erstellen der Klasse kein Wert angegeben wird.

@dataclass
class C:
    i: int
    j: int | None = None
    database: InitVar[DatabaseType | None] = None

    def __post_init__(self, database):
        if self.j is None and database is not None:
            self.j = database.lookup('j')

c = C(10, database=my_database)

In diesem Fall gibt fields() Field-Objekte für i und j zurück, aber nicht für database.

Gefrorene Instanzen

Es ist nicht möglich, wirklich unveränderliche Python-Objekte zu erstellen. Durch die Übergabe von frozen=True an den Dekorator @dataclass können Sie jedoch Unveränderlichkeit emulieren. In diesem Fall fügen Dataclasses die Methoden __setattr__() und __delattr__() zur Klasse hinzu. Diese Methoden lösen eine FrozenInstanceError aus, wenn sie aufgerufen werden.

Bei der Verwendung von frozen=True gibt es eine geringe Performance-Einbuße: __init__() kann keine einfache Zuweisung zur Initialisierung von Feldern verwenden und muss object.__setattr__() verwenden.

Vererbung

Wenn die Dataclass vom Dekorator @dataclass erstellt wird, durchsucht sie alle Basisklassen der Klasse in umgekehrter MRO (d.h. beginnend bei object) und fügt für jede gefundene Dataclass die Felder aus dieser Basisklasse zu einem geordneten Mapping von Feldern hinzu. Nachdem alle Felder der Basisklasse hinzugefügt wurden, werden die eigenen Felder zur geordneten Abbildung hinzugefügt. Alle generierten Methoden verwenden dieses kombinierte, berechnete geordnete Mapping von Feldern. Da die Felder in der Reihenfolge ihrer Einfügung liegen, überschreiben abgeleitete Klassen Basisklassen. Ein Beispiel

@dataclass
class Base:
    x: Any = 15.0
    y: int = 0

@dataclass
class C(Base):
    z: int = 10
    x: int = 15

Die endgültige Feldliste lautet in Reihenfolge x, y, z. Der endgültige Typ von x ist int, wie in der Klasse C angegeben.

Die generierte Methode __init__() für C sieht wie folgt aus:

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

Neuanordnung von Schlüsselwort-only Parametern in __init__()

Nachdem die für __init__() benötigten Parameter berechnet wurden, werden alle Schlüsselwort-only Parameter so verschoben, dass sie nach allen regulären (nicht-Schlüsselwort-only) Parametern stehen. Dies ist eine Anforderung, wie Schlüsselwort-only Parameter in Python implementiert werden: sie müssen nach nicht-Schlüsselwort-only Parametern kommen.

In diesem Beispiel sind Base.y, Base.w und D.t Schlüsselwort-only Felder, und Base.x und D.z sind reguläre Felder.

@dataclass
class Base:
    x: Any = 15.0
    _: KW_ONLY
    y: int = 0
    w: int = 1

@dataclass
class D(Base):
    z: int = 10
    t: int = field(kw_only=True, default=0)

Die generierte Methode __init__() für D sieht wie folgt aus:

def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):

Beachten Sie, dass die Parameter von der Reihenfolge, in der sie in der Feldliste erscheinen, umgeordnet wurden: Parameter, die aus regulären Feldern abgeleitet sind, folgen den Parametern, die aus Schlüsselwort-only Feldern abgeleitet sind.

Die relative Reihenfolge der Schlüsselwort-only Parameter wird in der neu geordneten Parameterliste der Methode __init__() beibehalten.

Default-Factory-Funktionen

Wenn ein field() einen default_factory angibt, wird dieser mit null Argumenten aufgerufen, wenn ein Standardwert für das Feld benötigt wird. Zum Beispiel, um eine neue Instanz einer Liste zu erstellen, verwenden Sie

mylist: list = field(default_factory=list)

Wenn ein Feld von __init__() ausgeschlossen wird (mit init=False) und das Feld auch eine default_factory angibt, dann wird die Default-Factory-Funktion immer aus der generierten __init__()-Funktion aufgerufen. Dies geschieht, weil es keine andere Möglichkeit gibt, dem Feld einen anfänglichen Wert zu geben.

Mutable Standardwerte

Python speichert Standardwerte für Mitgliedsvariablen in Klassenattributen. Betrachten Sie dieses Beispiel ohne Dataclasses

class C:
    x = []
    def add(self, element):
        self.x.append(element)

o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x is o2.x

Beachten Sie, dass sich die beiden Instanzen der Klasse C die gleiche Klassenvariable x teilen, wie erwartet.

Unter Verwendung von Dataclasses, *wenn* dieser Code gültig wäre.

@dataclass
class D:
    x: list = []      # This code raises ValueError
    def add(self, element):
        self.x.append(element)

würde er Code ähnlich zu

class D:
    x = []
    def __init__(self, x=x):
        self.x = x
    def add(self, element):
        self.x.append(element)

assert D().x is D().x

Dies hat das gleiche Problem wie das ursprüngliche Beispiel mit der Klasse C. Das heißt, zwei Instanzen der Klasse D, die beim Erstellen einer Klasseninstanz keinen Wert für x angeben, teilen sich dieselbe Kopie von x. Da Dataclasses einfach normale Python-Klassenerstellung verwenden, teilen sie sich auch dieses Verhalten. Es gibt keine allgemeine Möglichkeit für Data Classes, diesen Zustand zu erkennen. Stattdessen löst der Dekorator @dataclass einen ValueError aus, wenn er einen nicht hashbaren Standardparameter erkennt. Die Annahme ist, dass, wenn ein Wert nicht hashbar ist, er veränderlich ist. Dies ist eine Teillösung, schützt aber vor vielen häufigen Fehlern.

Die Verwendung von Default-Factory-Funktionen ist eine Möglichkeit, neue Instanzen von veränderlichen Typen als Standardwerte für Felder zu erstellen.

@dataclass
class D:
    x: list = field(default_factory=list)

assert D().x is not D().x

Geändert in Version 3.11: Anstatt nach Objekten vom Typ list, dict oder set zu suchen und diese zu verbieten, sind nun nicht hashbare Objekte als Standardwerte nicht erlaubt. Nicht-Hashbarkeit wird verwendet, um Veränderlichkeit zu approximieren.

Descriptor-typisierte Felder

Felder, denen Descriptor-Objekte als Standardwert zugewiesen werden, haben die folgenden besonderen Verhaltensweisen.

  • Der Wert für das Feld, der an die Methode __init__() der Dataclass übergeben wird, wird an die Methode __set__() des Descriptors übergeben, anstatt das Descriptor-Objekt zu überschreiben.

  • Ebenso wird beim Abrufen oder Setzen des Feldes die Methode __get__() oder die Methode __set__() des Descriptors aufgerufen, anstatt das Descriptor-Objekt zurückzugeben oder zu überschreiben.

  • Um festzustellen, ob ein Feld einen Standardwert enthält, ruft @dataclass die Methode __get__() des Descriptors in seiner Klassen-Zugriffsform auf: descriptor.__get__(obj=None, type=cls). Wenn der Descriptor in diesem Fall einen Wert zurückgibt, wird dieser als Standardwert des Feldes verwendet. Wenn der Descriptor in dieser Situation jedoch AttributeError auslöst, wird kein Standardwert für das Feld bereitgestellt.

class IntConversionDescriptor:
    def __init__(self, *, default):
        self._default = default

    def __set_name__(self, owner, name):
        self._name = "_" + name

    def __get__(self, obj, type):
        if obj is None:
            return self._default

        return getattr(obj, self._name, self._default)

    def __set__(self, obj, value):
        setattr(obj, self._name, int(value))

@dataclass
class InventoryItem:
    quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)

i = InventoryItem()
print(i.quantity_on_hand)   # 100
i.quantity_on_hand = 2.5    # calls __set__ with 2.5
print(i.quantity_on_hand)   # 2

Beachten Sie, dass, wenn ein Feld mit einem Descriptor-Typ annotiert ist, aber kein Descriptor-Objekt als Standardwert zugewiesen ist, das Feld wie ein normales Feld fungiert.