PEP 681 – Data Class Transforms
- Autor:
- Erik De Bonte <erikd at microsoft.com>, Eric Traut <erictr at microsoft.com>
- Sponsor:
- Jelle Zijlstra <jelle.zijlstra at gmail.com>
- Discussions-To:
- Typing-SIG-Thread
- Status:
- Final
- Typ:
- Standards Track
- Thema:
- Typisierung
- Erstellt:
- 02. Dez. 2021
- Python-Version:
- 3.11
- Post-History:
- 24. Apr. 2021, 13. Dez. 2021, 22. Feb. 2022
- Resolution:
- Python-Dev Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- Motivation
- Begründung
- Spezifikation
- Referenzimplementierung
- Abgelehnte Ideen
auto_attribs-Parametercmp-Parameter- Automatische Aliase für Feldnamen
- Alternative Algorithmen zur Feldreihenfolge
- In Unterklassen neu deklarierte Felder
- Django Primär- und Fremdschlüssel
- Klassenweite Standardwerte
- Unterstützung für Deskriptor-typisierte Felder
converter-Parameter für Feld-Spezifizierer
- Urheberrecht
Zusammenfassung
PEP 557 führte die Dataclass in die Python-Standardbibliothek ein. Mehrere beliebte Bibliotheken weisen Verhaltensweisen auf, die denen von Dataclasses ähneln, aber diese Verhaltensweisen können nicht mit Standard-Typannotationen beschrieben werden. Zu diesen Projekten gehören attrs, pydantic und Object-Relational Mapper (ORM)-Pakete wie SQLAlchemy und Django.
Die meisten Typ-Checker, Linter und Sprachserver unterstützen Dataclasses vollständig. Dieser Vorschlag zielt darauf ab, diese Funktionalität zu verallgemeinern und eine Möglichkeit für Drittanbieterbibliotheken bereitzustellen, anzugeben, dass bestimmte Dekoratorfunktionen, Klassen und Metaklassen Verhaltensweisen aufweisen, die Dataclasses ähneln.
Diese Verhaltensweisen umfassen
- Synthetisieren einer
__init__-Methode basierend auf deklarierten Datenfeldern. - Optionales Synthetisieren von Methoden
__eq__,__ne__,__lt__,__le__,__gt__und__ge__. - Unterstützung von „gefrorenen“ Klassen, einer Möglichkeit, Unveränderlichkeit während der statischen Typprüfung zu erzwingen.
- Unterstützung von „Feld-Spezifizierern“, die Attribute einzelner Felder beschreiben, über die ein statischer Typ-Checker informiert sein muss, wie z. B. ob für das Feld ein Standardwert angegeben ist.
Das vollständige Verhalten der Standard-Dataclass wird in der Python-Dokumentation beschrieben.
Dieser Vorschlag hat keine direkten Auswirkungen auf CPython, außer der Hinzufügung eines Dekorators dataclass_transform in typing.py.
Motivation
Es gibt keine bestehende, standardisierte Möglichkeit für Bibliotheken mit Dataclass-ähnlicher Semantik, ihr Verhalten gegenüber Typ-Checkern zu deklarieren. Um diese Einschränkung zu umgehen, wurden benutzerdefinierte Mypy-Plugins für viele dieser Bibliotheken entwickelt, aber diese Plugins funktionieren nicht mit anderen Typ-Checkern, Lintern oder Sprachservern. Sie sind auch für Bibliotheksautoren kostspielig zu warten, und sie erfordern, dass Python-Entwickler über die Existenz dieser Plugins Bescheid wissen und sie in ihrer Umgebung herunterladen und konfigurieren.
Begründung
Die Absicht dieses Vorschlags ist es nicht, jede Funktion jeder Bibliothek mit Dataclass-ähnlicher Semantik zu unterstützen, sondern vielmehr, die gängigsten Funktionen dieser Bibliotheken so zu nutzen, dass sie mit statischer Typprüfung kompatibel sind. Wenn ein Benutzer diese Bibliotheken schätzt und auch statische Typprüfung schätzt, muss er möglicherweise die Verwendung bestimmter Funktionen vermeiden oder geringfügige Anpassungen an seiner Nutzung vornehmen. Das ist bereits bei den benutzerdefinierten Mypy-Plugins der Fall, die nicht jede Funktion jeder Dataclass-ähnlichen Bibliothek unterstützen.
Wenn in Zukunft neue Funktionen zu Dataclasses hinzugefügt werden, beabsichtigen wir, wenn angemessen, auch die Unterstützung für diese Funktionen in dataclass_transform aufzunehmen. Das Synchronhalten dieser beiden Funktionssätze wird es Dataclass-Benutzern erleichtern, dataclass_transform zu verstehen und zu nutzen und vereinfacht die Wartung der Dataclass-Unterstützung in Typ-Checkern.
Darüber hinaus werden wir erwägen, in Zukunft dataclass_transform-Unterstützung für Funktionen hinzuzufügen, die von mehreren Drittanbieterbibliotheken übernommen wurden, aber nicht von Dataclasses unterstützt werden.
Spezifikation
Der Dekorator dataclass_transform
Diese Spezifikation führt eine neue Dekoratorfunktion im Modul typing namens dataclass_transform ein. Dieser Dekorator kann entweder auf eine Funktion angewendet werden, die selbst ein Dekorator ist, auf eine Klasse oder auf eine Metaklasse. Die Anwesenheit von dataclass_transform signalisiert einem statischen Typ-Checker, dass die dekorierte Funktion, Klasse oder Metaklasse zur Laufzeit „Magie“ durchführt, die eine Klasse transformiert und ihr Dataclass-ähnliche Verhaltensweisen verleiht.
Wenn dataclass_transform auf eine Funktion angewendet wird, wird angenommen, dass die Verwendung der dekorierten Funktion als Dekorator Dataclass-ähnliche Semantik anwendet. Wenn die Funktion Überladungen hat, kann der Dekorator dataclass_transform auf die Implementierung der Funktion oder eine beliebige, aber nicht mehr als eine, der Überladungen angewendet werden. Wenn er auf eine Überladung angewendet wird, beeinflusst der Dekorator dataclass_transform dennoch die gesamte Verwendung der Funktion.
Wenn dataclass_transform auf eine Klasse angewendet wird, wird Dataclass-ähnliche Semantik für jede Klasse angenommen, die von der dekorierten Klasse direkt oder indirekt erbt oder die dekorierte Klasse als Metaklasse verwendet. Attribute auf der dekorierten Klasse und ihren Basisklassen werden nicht als Felder betrachtet.
Beispiele für jeden Ansatz werden in den folgenden Abschnitten gezeigt. Jedes Beispiel erstellt eine Klasse CustomerModel mit Dataclass-ähnlicher Semantik. Die Implementierung der dekorierten Objekte ist zur besseren Lesbarkeit weggelassen, aber wir gehen davon aus, dass sie Klassen auf folgende Weise modifizieren:
- Sie synthetisieren eine
__init__-Methode unter Verwendung von Datenfeldern, die innerhalb der Klasse und ihrer Elternklassen deklariert sind. - Sie synthetisieren die Methoden
__eq__und__ne__.
Typ-Checker, die diesen PEP unterstützen, erkennen, dass die Klasse CustomerModel mithilfe der synthetisierten __init__-Methode instanziiert werden kann
# Using positional arguments
c1 = CustomerModel(327, "John Smith")
# Using keyword arguments
c2 = CustomerModel(id=327, name="John Smith")
# These calls will generate runtime errors and should be flagged as
# errors by a static type checker.
c3 = CustomerModel()
c4 = CustomerModel(327, first_name="John")
c5 = CustomerModel(327, "John Smith", 0)
Beispiel für Dekoratorfunktion
_T = TypeVar("_T")
# The ``create_model`` decorator is defined by a library.
# This could be in a type stub or inline.
@typing.dataclass_transform()
def create_model(cls: Type[_T]) -> Type[_T]:
cls.__init__ = ...
cls.__eq__ = ...
cls.__ne__ = ...
return cls
# The ``create_model`` decorator can now be used to create new model
# classes, like this:
@create_model
class CustomerModel:
id: int
name: str
Beispiel für Klasse
# The ``ModelBase`` class is defined by a library. This could be in
# a type stub or inline.
@typing.dataclass_transform()
class ModelBase: ...
# The ``ModelBase`` class can now be used to create new model
# subclasses, like this:
class CustomerModel(ModelBase):
id: int
name: str
Beispiel für Metaklasse
# The ``ModelMeta`` metaclass and ``ModelBase`` class are defined by
# a library. This could be in a type stub or inline.
@typing.dataclass_transform()
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
# The ``ModelBase`` class can now be used to create new model
# subclasses, like this:
class CustomerModel(ModelBase):
id: int
name: str
Parameter für Dekoratorfunktion und Klasse/Metaklasse
Ein Dekoratorfunktion, eine Klasse oder eine Metaklasse, die eine Dataclass-ähnliche Funktionalität bereitstellt, kann Parameter akzeptieren, die bestimmte Verhaltensweisen modifizieren. Diese Spezifikation definiert die folgenden Parameter, die statische Typ-Checker berücksichtigen müssen, wenn sie von einem Dataclass-Transform verwendet werden. Jeder dieser Parameter akzeptiert ein boolesches Argument, und der boolesche Wert (True oder False) muss statisch auswertbar sein.
eq,order,frozen,initundunsafe_hashsind Parameter, die in der Standard-Dataclass unterstützt werden und deren Bedeutung in PEP 557 definiert ist.kw_only,match_argsundslotssind Parameter, die in der Standard-Dataclass unterstützt werden und erstmals in Python 3.10 eingeführt wurden.
dataclass_transform-Parameter
Parameter für dataclass_transform ermöglichen einige grundlegende Anpassungen der Standardverhalten
_T = TypeVar("_T")
def dataclass_transform(
*,
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_specifiers: tuple[type | Callable[..., Any], ...] = (),
**kwargs: Any,
) -> Callable[[_T], _T]: ...
eq_defaultgibt an, ob der Parametereqals True oder False angenommen wird, wenn er vom Aufrufer weggelassen wird. Wenn nicht angegeben, wirdeq_defaultstandardmäßig auf True gesetzt (die Standardannahme für dataclass).order_defaultgibt an, ob der Parameterorderals True oder False angenommen wird, wenn er vom Aufrufer weggelassen wird. Wenn nicht angegeben, wirdorder_defaultstandardmäßig auf False gesetzt (die Standardannahme für dataclass).kw_only_defaultgibt an, ob der Parameterkw_onlyals True oder False angenommen wird, wenn er vom Aufrufer weggelassen wird. Wenn nicht angegeben, wirdkw_only_defaultstandardmäßig auf False gesetzt (die Standardannahme für dataclass).field_specifiersgibt eine statische Liste der unterstützten Klassen an, die Felder beschreiben. Einige Bibliotheken stellen auch Funktionen zur Zuweisung von Instanzen von Feld-Spezifizierern bereit, und diese Funktionen können ebenfalls in diesem Tupel angegeben werden. Wenn nicht angegeben, wirdfield_specifiersstandardmäßig auf ein leeres Tupel gesetzt (keine Feld-Spezifizierer unterstützt). Das Standardverhalten von Dataclass unterstützt nur einen Feld-Spezifizierer-Typ namensFieldzuzüglich einer Hilfsfunktion (field), die diese Klasse instanziiert. Wenn wir also das Standardverhalten von Dataclass beschreiben würden, würden wir das Tupelargument(dataclasses.Field, dataclasses.field)angeben.kwargserlaubt die Übergabe beliebiger zusätzlicher Schlüsselwortargumente andataclass_transform. Dies gibt Typ-Checkern die Freiheit, experimentelle Parameter zu unterstützen, ohne auf Änderungen intyping.pywarten zu müssen. Typ-Checker sollten Fehler für alle nicht erkannten Parameter melden.
In Zukunft können wir bei Bedarf weitere Parameter zu dataclass_transform hinzufügen, um gängige Verhaltensweisen in Benutzercode zu unterstützen. Diese Ergänzungen werden nach Erreichen eines Konsenses über typing-sig vorgenommen und nicht über zusätzliche PEPs.
Die folgenden Abschnitte enthalten weitere Beispiele, die zeigen, wie diese Parameter verwendet werden.
Beispiel für Dekoratorfunktion
# Indicate that the ``create_model`` function assumes keyword-only
# parameters for the synthesized ``__init__`` method unless it is
# invoked with ``kw_only=False``. It always synthesizes order-related
# methods and provides no way to override this behavior.
@typing.dataclass_transform(kw_only_default=True, order_default=True)
def create_model(
*,
frozen: bool = False,
kw_only: bool = True,
) -> Callable[[Type[_T]], Type[_T]]: ...
# Example of how this decorator would be used by code that imports
# from this library:
@create_model(frozen=True, kw_only=False)
class CustomerModel:
id: int
name: str
Beispiel für Klasse
# Indicate that classes that derive from this class default to
# synthesizing comparison methods.
@typing.dataclass_transform(eq_default=True, order_default=True)
class ModelBase:
def __init_subclass__(
cls,
*,
init: bool = True,
frozen: bool = False,
eq: bool = True,
order: bool = True,
):
...
# Example of how this class would be used by code that imports
# from this library:
class CustomerModel(
ModelBase,
init=False,
frozen=True,
eq=False,
order=False,
):
id: int
name: str
Beispiel für Metaklasse
# Indicate that classes that use this metaclass default to
# synthesizing comparison methods.
@typing.dataclass_transform(eq_default=True, order_default=True)
class ModelMeta(type):
def __new__(
cls,
name,
bases,
namespace,
*,
init: bool = True,
frozen: bool = False,
eq: bool = True,
order: bool = True,
):
...
class ModelBase(metaclass=ModelMeta):
...
# Example of how this class would be used by code that imports
# from this library:
class CustomerModel(
ModelBase,
init=False,
frozen=True,
eq=False,
order=False,
):
id: int
name: str
Feld-Spezifizierer
Die meisten Bibliotheken, die Dataclass-ähnliche Semantik unterstützen, stellen einen oder mehrere „Feld-Spezifizierer“-Typen bereit, die es einer Klassendefinition ermöglichen, zusätzliche Metadaten für jedes Feld in der Klasse bereitzustellen. Diese Metadaten können beispielsweise Standardwerte beschreiben oder angeben, ob das Feld in die synthetisierte __init__-Methode aufgenommen werden soll.
Feld-Spezifizierer können in Fällen weggelassen werden, in denen keine zusätzlichen Metadaten erforderlich sind
@dataclass
class Employee:
# Field with no specifier
name: str
# Field that uses field specifier class instance
age: Optional[int] = field(default=None, init=False)
# Field with type annotation and simple initializer to
# describe default value
is_paid_hourly: bool = True
# Not a field (but rather a class variable) because type
# annotation is not provided.
office_number = "unassigned"
Parameter für Feld-Spezifizierer
Bibliotheken, die Dataclass-ähnliche Semantik unterstützen und Feld-Spezifizierer-Klassen unterstützen, verwenden typischerweise gängige Parameternamen zur Erstellung dieser Feld-Spezifizierer. Diese Spezifikation formalisiert die Namen und Bedeutungen der Parameter, die für statische Typ-Checker verstanden werden müssen. Diese standardisierten Parameter müssen schlüsselwortbasiert sein.
Diese Parameter sind eine Obermenge der von dataclasses.field unterstützten Parameter, ausgenommen jene, die keine Auswirkungen auf die Typ-Prüfung haben, wie z. B. compare und hash.
Feld-Spezifizierer-Klassen dürfen andere Parameter in ihren Konstruktoren verwenden, und diese Parameter können positionsabhängig sein und andere Namen verwenden.
initist ein optionaler boolescher Parameter, der angibt, ob das Feld in die synthetisierte__init__-Methode aufgenommen werden soll. Wenn nicht angegeben, istinitstandardmäßig True. Feld-Spezifizierer-Funktionen können Überladungen verwenden, die implizit den Wert voninitmithilfe eines Literal-Booleschen Werttyps (Literal[False]oderLiteral[True]) angeben.defaultist ein optionaler Parameter, der den Standardwert für das Feld angibt.default_factoryist ein optionaler Parameter, der einen Laufzeit-Callback bereitstellt, der den Standardwert für das Feld zurückgibt. Wenn wederdefaultnochdefault_factoryangegeben sind, wird angenommen, dass das Feld keinen Standardwert hat und bei der Instanziierung der Klasse mit einem Wert versehen werden muss.factoryist ein Alias fürdefault_factory. Standard-Dataclasses verwenden den Namendefault_factory, aber attrs verwendet in vielen Szenarien den Namenfactory, daher ist dieser Alias für die Unterstützung von attrs notwendig.kw_onlyist ein optionaler boolescher Parameter, der angibt, ob das Feld als nur-schlüsselwortbasiert markiert werden soll. Wenn True, ist das Feld nur-schlüsselwortbasiert. Wenn False, ist es nicht nur-schlüsselwortbasiert. Wenn nicht angegeben, wird der Wert des Parameterskw_onlydes mitdataclass_transformdekorierten Objekts verwendet, oder wenn dieser nicht angegeben ist, wird der Wert vonkw_only_defaultaufdataclass_transformverwendet.aliasist ein optionaler String-Parameter, der einen alternativen Namen für das Feld angibt. Dieser alternative Name wird in der synthetisierten__init__-Methode verwendet.
Es ist ein Fehler, mehr als einen der Parameter default, default_factory und factory anzugeben.
Dieses Beispiel demonstriert die oben genannten Punkte
# Library code (within type stub or inline)
# In this library, passing a resolver means that init must be False,
# and the overload with Literal[False] enforces that.
@overload
def model_field(
*,
default: Optional[Any] = ...,
resolver: Callable[[], Any],
init: Literal[False] = False,
) -> Any: ...
@overload
def model_field(
*,
default: Optional[Any] = ...,
resolver: None = None,
init: bool = True,
) -> Any: ...
@typing.dataclass_transform(
kw_only_default=True,
field_specifiers=(model_field, ))
def create_model(
*,
init: bool = True,
) -> Callable[[Type[_T]], Type[_T]]: ...
# Code that imports this library:
@create_model(init=False)
class CustomerModel:
id: int = model_field(resolver=lambda : 0)
name: str
Laufzeitverhalten
Zur Laufzeit ist die einzige Auswirkung des Dekorators dataclass_transform die Setzung eines Attributs namens __dataclass_transform__ auf der dekorierten Funktion oder Klasse zur Unterstützung der Introspektion. Der Wert des Attributs sollte ein Dictionary sein, das die Namen der dataclass_transform-Parameter auf ihre Werte abbildet.
Zum Beispiel:
{
"eq_default": True,
"order_default": False,
"kw_only_default": False,
"field_specifiers": (),
"kwargs": {}
}
Dataclass-Semantik
Sofern in diesem PEP nicht anders angegeben, wird angenommen, dass Klassen, die von dataclass_transform betroffen sind, sei es durch Vererbung von einer Klasse, die mit dataclass_transform dekoriert ist, oder durch Dekoration mit einer Funktion, die mit dataclass_transform dekoriert ist, wie die Standard-dataclass verhalten.
Dies umfasst unter anderem die folgenden Semantiken
- Gefrorene Dataclasses dürfen nicht von nicht-gefrorenen Dataclasses erben. Eine Klasse, die mit
dataclass_transformdekoriert wurde, gilt weder als gefroren noch als nicht-gefroren, was es gefrorenen Klassen erlaubt, von ihr zu erben. Ähnlich gilt eine Klasse, die direkt eine Metaklasse angibt, die mitdataclass_transformdekoriert ist, weder als gefroren noch als nicht-gefroren.Betrachten Sie diese Klassenbeispiele
# ModelBase is not considered either "frozen" or "non-frozen" # because it is decorated with ``dataclass_transform`` @typing.dataclass_transform() class ModelBase(): ... # Vehicle is considered non-frozen because it does not specify # "frozen=True". class Vehicle(ModelBase): name: str # Car is a frozen class that derives from Vehicle, which is a # non-frozen class. This is an error. class Car(Vehicle, frozen=True): wheel_count: int
Und diese ähnlichen Metaklassenbeispiele
@typing.dataclass_transform() class ModelMeta(type): ... # ModelBase is not considered either "frozen" or "non-frozen" # because it directly specifies ModelMeta as its metaclass. class ModelBase(metaclass=ModelMeta): ... # Vehicle is considered non-frozen because it does not specify # "frozen=True". class Vehicle(ModelBase): name: str # Car is a frozen class that derives from Vehicle, which is a # non-frozen class. This is an error. class Car(Vehicle, frozen=True): wheel_count: int
- Die Feldreihenfolge und Vererbung wird angenommen, dass sie den in 557 angegebenen Regeln folgt. Dies schließt die Auswirkungen von Überschreibungen (Neudefinition eines Feldes in einer Kindklasse, das bereits in einer Elternklasse definiert wurde) ein.
- PEP 557 gibt an, dass alle Felder ohne Standardwerte vor Feldern mit Standardwerten erscheinen müssen. Obwohl dies in PEP 557 nicht ausdrücklich erwähnt wird, wird diese Regel ignoriert, wenn
init=Falsegilt, und diese Spezifikation ignoriert diese Anforderung ebenfalls in dieser Situation. Ebenso ist es nicht notwendig, diese Reihenfolge zu erzwingen, wenn schlüsselwortbasierte Parameter für__init__verwendet werden, daher wird die Regel nicht erzwungen, wenn die Semantik vonkw_onlyin Kraft ist. - Wie bei
dataclasswird die Methodensynthese übersprungen, wenn sie eine Methode überschreiben würde, die explizit innerhalb der Klasse deklariert ist. Methoden-Deklarationen auf Basisklassen führen nicht dazu, dass die Methodensynthese übersprungen wird.Wenn eine Klasse beispielsweise explizit eine
__init__-Methode deklariert, wird für diese Klasse keine__init__-Methode synthetisiert. - KW_ONLY-Sentinel-Werte werden unterstützt, wie in der Python-Dokumentation und bpo-43532 beschrieben.
- ClassVar-Attribute werden nicht als Dataclass-Felder betrachtet und werden von Dataclass-Mechanismen ignoriert.
Undefiniertes Verhalten
Wenn mehrere dataclass_transform-Dekoratoren gefunden werden, entweder auf einer einzelnen Funktion (einschließlich ihrer Überladungen), einer einzelnen Klasse oder innerhalb einer Klassenvererbung, ist das daraus resultierende Verhalten undefiniert. Bibliotheksautoren sollten solche Szenarien vermeiden.
Referenzimplementierung
Pyright enthält die Referenzimplementierung für Typ-Checker-Unterstützung für dataclass_transform. Pyrights dataClasses.ts Quelldatei wäre ein guter Ausgangspunkt für das Verständnis der Implementierung.
Die Bibliotheken attrs und pydantic verwenden dataclass_transform und dienen als reale Beispiele für dessen Verwendung.
Abgelehnte Ideen
auto_attribs-Parameter
Die attrs-Bibliothek unterstützt einen Parameter auto_attribs, der angibt, ob Klassenmitglieder, die mit PEP 526-Variablenannotationen dekoriert sind, aber keine Zuweisung haben, als Datenfelder behandelt werden sollen.
Wir haben erwogen, auto_attribs und einen entsprechenden Parameter auto_attribs_default zu unterstützen, haben uns aber dagegen entschieden, da er spezifisch für attrs ist.
Django unterstützt die Deklaration von Feldern nicht nur anhand von Typannotationen, daher sollten sich Django-Benutzer, die dataclass_transform nutzen, bewusst sein, dass sie immer zugewiesene Werte angeben sollten.
cmp-Parameter
Die attrs-Bibliothek unterstützt einen booleschen Parameter cmp, der dem Setzen von eq und order auf True entspricht. Wir haben uns entschieden, keinen cmp-Parameter zu unterstützen, da er nur für attrs gilt. Benutzer können das cmp-Verhalten emulieren, indem sie stattdessen die Parameternamen eq und order verwenden.
Automatische Aliase für Feldnamen
Die attrs-Bibliothek führt automatische Aliase für Feldnamen durch, die mit einem einzigen Unterstrich beginnen, wobei der Unterstrich aus dem Namen des entsprechenden __init__-Parameters entfernt wird.
Dieser Vorschlag verzichtet auf dieses Verhalten, da es spezifisch für attrs ist. Benutzer können diese Felder manuell mit dem Parameter alias aliasen.
Alternative Algorithmen zur Feldreihenfolge
Die attrs-Bibliothek unterstützt derzeit zwei Ansätze zur Reihenfolge der Felder innerhalb einer Klasse
- Dataclass-Reihenfolge: Die gleiche Reihenfolge, die von Dataclasses verwendet wird. Dies ist das Standardverhalten der älteren APIs (z. B.
attr.s). - Method Resolution Order (MRO): Dies ist das Standardverhalten der neueren APIs (z. B. define, mutable, frozen). Ältere APIs (z. B.
attr.s) können dieses Verhalten durch Angabe voncollect_by_mro=Trueübernehmen.
Die resultierenden Feldreihenfolgen können in bestimmten diamantförmigen Mehrfachvererbungsszenarien abweichen.
Der Einfachheit halber unterstützt dieser Vorschlag keine andere Feldreihenfolge als die von Dataclasses verwendete.
In Unterklassen neu deklarierte Felder
Die attrs-Bibliothek unterscheidet sich von Standard-Dataclasses darin, wie sie mit geerbten Feldern umgeht, die in Unterklassen neu deklariert werden. Die Dataclass-Spezifikation bewahrt die ursprüngliche Reihenfolge, aber attrs definiert eine neue Reihenfolge basierend auf Unterklassen.
Der Einfachheit halber haben wir uns entschieden, nur das Dataclass-Verhalten zu unterstützen. Benutzer von attrs, die sich auf die attrs-spezifische Reihenfolge verlassen, werden die erwartete Reihenfolge der Parameter in der synthetisierten __init__-Methode nicht sehen.
Django Primär- und Fremdschlüssel
Django wendet zusätzliche Logik für Primär- und Fremdschlüssel an. Zum Beispiel fügt es automatisch ein Feld id (und einen __init__-Parameter) hinzu, wenn kein Feld als Primärschlüssel ausgewiesen ist.
Da dies nicht für Dataclass-Bibliotheken allgemein anwendbar ist, wird diese zusätzliche Logik mit diesem Vorschlag nicht berücksichtigt, so dass Benutzer von Django explizit das Feld id deklarieren müssten.
Klassenweite Standardwerte
SQLAlchemy hat uns gebeten, eine Möglichkeit zur Verfügung zu stellen, um anzugeben, dass der Standardwert aller Felder in der transformierten Klasse None ist. Es ist typisch, dass alle SQLAlchemy-Felder optional sind, und None zeigt an, dass das Feld nicht gesetzt ist.
Wir haben uns entschieden, diese Funktion nicht zu unterstützen, da sie spezifisch für SQLAlchemy ist. Benutzer können stattdessen manuell default=None für diese Felder festlegen.
Unterstützung für Deskriptor-typisierte Felder
Wir haben erwogen, einen booleschen Parameter auf dataclass_transform hinzuzufügen, um eine bessere Unterstützung für Felder mit Deskriptortypen zu ermöglichen, was in SQLAlchemy üblich ist. Wenn dies aktiviert ist, wäre der Typ jedes Parameters in der synthetisierten __init__-Methode, der einem Deskriptor-typisierten Feld entspricht, der Typ des Wertparameters der __set__-Methode des Deskriptors und nicht der Deskriptortyp selbst. Ebenso wäre beim Setzen des Feldes der __set__-Werttyp zu erwarten. Und beim Abrufen des Feldwertes wäre sein Typ so zu erwarten, dass er mit dem Rückgabetyp von __get__ übereinstimmt.
Diese Idee basierte auf der Annahme, dass dataclass Deskriptor-typisierte Felder nicht richtig unterstützt. Tatsächlich tut es das, aber Typ-Checker (zumindest mypy und pyright) spiegelten das Laufzeitverhalten nicht wider, was zu unserem Missverständnis führte. Weitere Details finden Sie im Pyright-Bug.
converter-Parameter für Feld-Spezifizierer
Die attrs-Bibliothek unterstützt einen Feld-Spezifizierer-Parameter converter, der ein Callable ist, der von der generierten __init__-Methode aufgerufen wird, um den bereitgestellten Wert in einen anderen gewünschten Wert zu konvertieren. Dies ist schwierig zu unterstützen, da der Parametertyp in der synthetisierten __init__-Methode nicht abgedeckte Werte akzeptieren muss, aber das resultierende Feld entsprechend der Ausgabe des Konverters typisiert wird.
Einige Aspekte dieses Problems sind in einer Pyright-Diskussion detailliert.
Es gibt möglicherweise keine gute Möglichkeit, dies zu unterstützen, da nicht genügend Informationen vorhanden sind, um den Typ des Eingabeparameters abzuleiten. Eine mögliche Lösung wäre, die Unterstützung für einen Feld-Spezifizierer-Parameter converter hinzuzufügen, aber dann den Any-Typ für den entsprechenden Parameter in der __init__-Methode zu verwenden.
Urheberrecht
Dieses Dokument wird in die Public Domain oder unter die CC0-1.0-Universal-Lizenz gestellt, je nachdem, welche Lizenz permissiver ist.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0681.rst
Zuletzt geändert: 2025-02-01 07:28:42 GMT