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
@dataclassuntersucht die Klasse auffields. Einfieldwird als Klassenvariable definiert, die eine Typannotation hat. Mit zwei Ausnahmen, die unten beschrieben werden, untersucht@dataclassnicht 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
@dataclassfü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
@dataclasseinfach 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@dataclasssind ä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
@dataclasssind: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 einValueErrorausgelöst.Wenn die Klasse bereits eine der Methoden
__lt__(),__le__(),__gt__()oder__ge__()definiert, wird einTypeErrorausgelöst.unsafe_hash: Wenn wahr, erzwingt
dataclassesdie 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 istFalse.__hash__()wird von der integrierten Funktionhash()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@dataclassDekorator abhängt.Standardmäßig fügt
@dataclassnicht 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__ = Nonehat eine spezielle Bedeutung für Python, wie in der Dokumentation zu__hash__()beschrieben.Wenn
__hash__()nicht explizit definiert ist oder wenn es aufNonegesetzt ist, *kann*@dataclasseine implizite__hash__()Methode hinzufügen. Obwohl nicht empfohlen, können Sie@dataclassmitunsafe_hash=Truezwingen, 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 nochunsafe_hash=Truesetzen können; dies führt zu einemTypeError.Wenn eq und frozen beide wahr sind, generiert
@dataclassstandardmäßig eine__hash__()Methode für Sie. Wenn eq wahr und frozen falsch ist, wird__hash__()aufNonegesetzt, 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 Oberklasseobjectist, 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 einTypeErrorausgelö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 AbschnittKW_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 einTypeErrorausgelöst.
Warnung
Die Übergabe von Parametern an die
__init_subclass__()einer Basisklasse bei Verwendung vonslots=Trueführt zu einemTypeError. 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 stattdessenfields(). 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 Instanzweakref-fähigzu machen. Es ist ein Fehler,weakref_slot=Trueanzugeben, ohne auchslots=Trueanzugeben.
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
aals auchbin die hinzugefügte__init__()Methode aufgenommen, die wie folgt definiert wird:def __init__(self, a: int, b: int = 0):
Ein
TypeErrorwird 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
MISSINGein Sentinel-Objekt, das verwendet wird, um zu erkennen, ob einige Parameter vom Benutzer bereitgestellt werden. Dieses Sentinel wird verwendet, weilNoneein gültiger Wert für einige Parameter mit einer eindeutigen Bedeutung ist. Kein Code sollte den WertMISSINGdirekt 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
Nonesein. Wenn wahr, wird dieses Feld in die generierte__hash__()Methode aufgenommen. Wenn falsch, wird dieses Feld aus der generierten__hash__()Methode ausgeschlossen. WennNone(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 alsNonewird nicht empfohlen.Ein möglicher Grund,
hash=False, abercompare=Truezu 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
Nonesein.Nonewird als leeres Dict behandelt. Dieser Wert wird inMappingProxyType()verpackt, um ihn schreibgeschützt zu machen, und imField-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@dataclassdie 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.zwird10sein, das KlassenattributC.twird20sein, und die KlassenattributeC.xundC.ywerden nicht gesetzt.
- class dataclasses.Field¶
FieldObjekte beschreiben jedes definierte Feld. Diese Objekte werden intern erstellt und von der Modulmethodefields()zurückgegeben (siehe unten). Benutzer sollten niemals einFieldObjekt direkt instanziieren. Seine dokumentierten Attribute sind:name: Der Name des Feldes.type: Der Typ des Feldes.default,default_factory,init,repr,hash,compare,metadataundkw_onlyhaben die gleiche Bedeutung und Werte wie in der Funktionfield().
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 mitInitVarannotiert sind, werden als Pseudo-Felder betrachtet und daher weder von der Funktionfields()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
FieldObjekten zurück, die die Felder für diese Dataclass definieren. Akzeptiert entweder eine Dataclass oder eine Instanz einer Dataclass. LöstTypeErroraus, wenn keine Dataclass oder Instanz davon übergeben wird. Gibt keine Pseudo-Felder zurück, dieClassVaroderInitVarsind.
- 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: valuePaare. Dataclasses, Dicts, Listen und Tupel werden rekursiv durchlaufen. Andere Objekte werden mitcopy.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östTypeErroraus, 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östTypeErroraus, 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 nurnameangegeben wird, wirdtyping.Anyfürtypeverwendet. 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
@dataclasserhalten. Standardmäßig wird die Funktion@dataclassverwendet.Diese Funktion ist nicht unbedingt erforderlich, da jeder Python-Mechanismus zur Erstellung einer neuen Klasse mit
__annotations__anschließend die Funktion@dataclassanwenden kann, um diese Klasse in eine Dataclass zu konvertieren. Diese Funktion wird als Bequemlichkeit bereitgestellt. Zum BeispielC = 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
TypeErrorausgelöst. Wenn Schlüssel in changes keine Feldnamen der gegebenen Dataclass sind, wirdTypeErrorausgelö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=Falsedefiniert sind. In diesem Fall wird einValueErrorausgelöst.Seien Sie sich bewusst, wie Felder mit
init=Falsewährend eines Aufrufs vonreplace()funktionieren. Sie werden nicht vom Quellobjekt kopiert, sondern werden in__post_init__()initialisiert, falls sie überhaupt initialisiert werden. Es wird erwartet, dass Felder mitinit=Falseselten und mit Bedacht verwendet werden. Wenn sie verwendet werden, kann es ratsam sein, alternative Klassenkonstruktoren oder vielleicht eine benutzerdefinierte Methodereplace()(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
Truezurück, wenn der Parameter eine Dataclass (einschließlich Unterklassen einer Dataclass) oder eine Instanz davon ist, andernfalls gibt esFalsezurü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_ONLYwerden als Schlüsselwort-only Felder markiert. Beachten Sie, dass ein Pseudo-Feld vom TypKW_ONLYansonsten völlig ignoriert wird. Dies gilt auch für den Namen eines solchen Feldes. Konventionsgemäß wird der Name_für ein Feld vom TypKW_ONLYverwendet. 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
yundzals 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_ONLYist.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 mitfrozen=Truedefiniert wurde. Sie ist eine Unterklasse vonAttributeError.
Nachinitialisierungsverarbeitung¶
- dataclasses.__post_init__()¶
Wenn sie auf der Klasse definiert ist, wird sie von der generierten Methode
__init__()aufgerufen, normalerweise alsself.__post_init__(). Wenn jedochInitVar-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
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
@dataclassdie 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 jedochAttributeErrorauslö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.