PEP 589 – TypedDict: Typisierung für Wörterbücher mit einer festen Anzahl von Schlüsseln
- Autor:
- Jukka Lehtosalo <jukka.lehtosalo at iki.fi>
- Sponsor:
- Guido van Rossum <guido at python.org>
- BDFL-Delegate:
- Guido van Rossum <guido at python.org>
- Discussions-To:
- Typing-SIG list
- Status:
- Final
- Typ:
- Standards Track
- Thema:
- Typisierung
- Erstellt:
- 20. März 2019
- Python-Version:
- 3.8
- Post-History:
- Resolution:
- Typing-SIG Nachricht
Zusammenfassung
PEP 484 definiert den Typ Dict[K, V] für uniforme Wörterbücher, bei denen jeder Wert denselben Typ hat und beliebige Schlüsselwerte unterstützt werden. Es unterstützt nicht richtig das gängige Muster, bei dem der Typ eines Wörterbuchwerts vom String-Wert des Schlüssels abhängt. Dieses PEP schlägt einen Typkonstruktor typing.TypedDict vor, um den Anwendungsfall zu unterstützen, bei dem ein Wörterbuchobjekt eine spezifische Menge von String-Schlüsseln hat, die jeweils einen Wert eines spezifischen Typs haben.
Hier ist ein Beispiel, bei dem PEP 484 uns nicht erlaubt, zufriedenstellend zu annotieren
movie = {'name': 'Blade Runner',
'year': 1982}
Dieses PEP schlägt die Hinzufügung eines neuen Typkonstruktors namens TypedDict vor, um den Typ von movie präzise darstellen zu können
from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
Nun sollte ein Typüberprüfer diesen Code akzeptieren
movie: Movie = {'name': 'Blade Runner',
'year': 1982}
Motivation
Das Darstellen eines Objekts oder strukturierter Daten mittels (potenziell verschachtelter) Wörterbücher mit String-Schlüsseln (anstelle einer benutzerdefinierten Klasse) ist ein gängiges Muster in Python-Programmen. Die Darstellung von JSON-Objekten ist vielleicht der kanonische Anwendungsfall, und dies ist so beliebt, dass Python eine JSON-Bibliothek mitliefert. Dieses PEP schlägt eine Möglichkeit vor, solchen Code effektiver typisieren zu lassen.
Allgemeiner gesagt hatte die Darstellung reiner Datenobjekte unter ausschließlicher Verwendung von Python-Primitivtypen wie Wörterbüchern, Strings und Listen eine gewisse Anziehungskraft. Sie sind leicht zu serialisieren und deserialisieren, auch wenn kein JSON verwendet wird. Sie unterstützen trivial verschiedene nützliche Operationen ohne zusätzlichen Aufwand, einschließlich Pretty-Printing (über str() und das pprint-Modul), Iteration und Gleichheitsvergleiche.
PEP 484 unterstützt die oben genannten Anwendungsfälle nicht richtig. Betrachten wir ein Wörterbuchobjekt, das genau zwei gültige String-Schlüssel hat: 'name' mit dem Werttyp str und 'year' mit dem Werttyp int. Der PEP 484-Typ Dict[str, Any] wäre geeignet, ist aber zu nachgiebig, da beliebige String-Schlüssel verwendet werden können und beliebige Werte gültig sind. Ebenso ist Dict[str, Union[str, int]] zu allgemein, da der Wert für den Schlüssel 'name' eine int sein könnte und beliebige String-Schlüssel erlaubt sind. Auch wäre der Typ eines Abonnement-Ausdrucks wie d['name'] (vorausgesetzt d ist ein Wörterbuch dieses Typs) Union[str, int], was zu breit ist.
Dataclasses sind eine neuere Alternative zur Lösung dieses Anwendungsfalls, aber es gibt immer noch viel bestehenden Code, der vor der Verfügbarkeit von Dataclasses geschrieben wurde, insbesondere in großen bestehenden Codebasen, in denen Typisierung und Überprüfung hilfreich waren. Im Gegensatz zu Wörterbuchobjekten unterstützen Dataclasses nicht direkt die JSON-Serialisierung, obwohl ein Drittanbieterpaket dies implementiert [1].
Spezifikation
Ein TypedDict-Typ repräsentiert Wörterbuchobjekte mit einer spezifischen Menge von String-Schlüsseln und mit spezifischen Werttypen für jeden gültigen Schlüssel. Jeder String-Schlüssel kann entweder erforderlich (er muss vorhanden sein) oder nicht erforderlich (er muss nicht vorhanden sein) sein.
Dieses PEP schlägt zwei Möglichkeiten zur Definition von TypedDict-Typen vor. Die erste verwendet eine klassenbasierte Syntax. Die zweite ist eine alternative zuweisungsbasierte Syntax, die zur Abwärtskompatibilität bereitgestellt wird, um das Feature auf ältere Python-Versionen zurückportieren zu können. Die Begründung ist ähnlich zu der, warum PEP 484 eine kommentarbasierten Annotationssyntax für Python 2.7 unterstützt: Typisierung ist besonders nützlich für große bestehende Codebasen, und diese müssen oft auf älteren Python-Versionen laufen. Die beiden Syntaxoptionen spiegeln die von typing.NamedTuple unterstützten Syntaxvarianten wider. Weitere vorgeschlagene Features umfassen TypedDict-Vererbung und Vollständigkeit (Totality) (Festlegung, ob Schlüssel erforderlich sind oder nicht).
Dieses PEP liefert auch einen Entwurf, wie ein Typüberprüfer die Typüberprüfung von Operationen mit TypedDict-Objekten unterstützen soll. Ähnlich wie bei PEP 484 bleibt diese Diskussion bewusst etwas vage, um Experimente mit einer Vielzahl von unterschiedlichen Typüberprüfungsansätzen zu ermöglichen. Insbesondere sollte die Typsicherheit auf struktureller Kompatibilität basieren: Ein spezifischerer TypedDict-Typ kann mit einem kleineren (allgemeineren) TypedDict-Typ kompatibel sein.
Klassenbasierte Syntax
Ein TypedDict-Typ kann mit der klassenbasierten Definitionssyntax definiert werden, wobei typing.TypedDict die einzige Basisklasse ist
from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
Movie ist ein TypedDict-Typ mit zwei Einträgen: 'name' (mit Typ str) und 'year' (mit Typ int).
Ein Typüberprüfer sollte validieren, dass der Körper einer klassenbasierten TypedDict-Definition den folgenden Regeln entspricht
- Der Klassenkörper sollte nur Zeilen mit Eintragsdefinitionen der Form
key: value_typeenthalten, optional vorangestellt von einem Docstring. Die Syntax für Eintragsdefinitionen ist identisch zu Attributannotationen, aber es darf keine Initialisierung geben, und der Schlüsselname bezieht sich tatsächlich auf den String-Wert des Schlüssels anstelle eines Attributnamens. - Typ-Kommentare können mit der klassenbasierten Syntax nicht verwendet werden, um Konsistenz mit der klassenbasierten
NamedTuple-Syntax zu gewährleisten. (Beachten Sie, dass es zur Abwärtskompatibilität mit Python 2.7 nicht ausreichen würde, Typ-Kommentare zu unterstützen, da die Klassendefinition eintotal-Schlüsselwortargument enthalten kann, wie unten diskutiert, und dies in Python 2.7 keine gültige Syntax ist.) Stattdessen bietet dieses PEP eine alternative, zuweisungsbasierte Syntax für Abwärtskompatibilität, die in Alternative Syntax diskutiert wird. - String-Literal-Vorwärtsreferenzen sind in den Werttypen gültig.
- Methoden sind nicht erlaubt, da der Laufzeittyp eines TypedDict-Objekts immer nur
dictist (es ist niemals eine Unterklasse vondict). - Die Angabe einer Metaklasse ist nicht erlaubt.
Ein leeres TypedDict kann erstellt werden, indem nur pass im Körper enthalten ist (wenn ein Docstring vorhanden ist, kann pass weggelassen werden)
class EmptyDict(TypedDict):
pass
Verwendung von TypedDict-Typen
Hier ist ein Beispiel, wie der Typ Movie verwendet werden kann
movie: Movie = {'name': 'Blade Runner',
'year': 1982}
Eine explizite Movie-Typannotation ist im Allgemeinen erforderlich, da ansonsten ein gewöhnlicher Wörterbuchtyp von einem Typüberprüfer angenommen werden könnte, zur Abwärtskompatibilität. Wenn ein Typüberprüfer schlussfolgern kann, dass ein konstruiertes Wörterbuchobjekt ein TypedDict sein soll, kann eine explizite Annotation weggelassen werden. Ein typisches Beispiel ist ein Wörterbuchobjekt als Funktionsargument. In diesem Beispiel wird von einem Typüberprüfer erwartet, dass er schlussfolgert, dass das Wörterbuchargument als TypedDict verstanden werden sollte
def record_movie(movie: Movie) -> None: ...
record_movie({'name': 'Blade Runner', 'year': 1982})
Ein weiteres Beispiel, bei dem ein Typüberprüfer eine Wörterbuchanzeige als TypedDict behandeln sollte, ist die Zuweisung an eine Variable mit einem zuvor deklarierten TypedDict-Typ
movie: Movie
...
movie = {'name': 'Blade Runner', 'year': 1982}
Operationen auf movie können von einem statischen Typüberprüfer geprüft werden
movie['director'] = 'Ridley Scott' # Error: invalid key 'director'
movie['year'] = '1982' # Error: invalid value type ("int" expected)
Der folgende Code sollte abgelehnt werden, da 'title' kein gültiger Schlüssel ist und der Schlüssel 'name' fehlt
movie2: Movie = {'title': 'Blade Runner',
'year': 1982}
Das erstellte TypedDict-Typobjekt ist kein echtes Klassenobjekt. Hier sind die einzigen Verwendungen des Typs, die ein Typüberprüfer zu erlauben erwartet
- Es kann in Typannotationen und in jedem Kontext verwendet werden, in dem ein beliebiger Typ-Hinweis gültig ist, wie z. B. in Typ-Aliassen und als Zieltyp eines Casts.
- Es kann als aufrufbares Objekt mit Schlüsselwortargumenten verwendet werden, die den TypedDict-Einträgen entsprechen. Nicht-Schlüsselwortargumente sind nicht erlaubt. Beispiel
m = Movie(name='Blade Runner', year=1982)
Beim Aufruf gibt das TypedDict-Typobjekt zur Laufzeit ein gewöhnliches Wörterbuchobjekt zurück
print(type(m)) # <class 'dict'>
- Es kann als Basisklasse verwendet werden, aber nur bei der Definition eines abgeleiteten TypedDict. Dies wird unten detaillierter diskutiert.
Insbesondere können TypedDict-Typobjekte nicht in isinstance()-Tests wie isinstance(d, Movie) verwendet werden. Der Grund dafür ist, dass es keine bestehende Unterstützung für die Überprüfung von Typen von Wörterbuch-Elementwerten gibt, da isinstance() nicht mit vielen PEP 484-Typen funktioniert, einschließlich gängiger wie List[str]. Dies wäre für Fälle wie diesen erforderlich
class Strings(TypedDict):
items: List[str]
print(isinstance({'items': [1]}, Strings)) # Should be False
print(isinstance({'items': ['x']}, Strings)) # Should be True
Der obige Anwendungsfall wird nicht unterstützt. Dies entspricht der Tatsache, dass isinstance() für List[str] nicht unterstützt wird.
Vererbung
Es ist möglich, dass ein TypedDict-Typ mithilfe der klassenbasierten Syntax von einem oder mehreren TypedDict-Typen erbt. In diesem Fall sollte die TypedDict-Basisklasse nicht enthalten sein. Beispiel
class BookBasedMovie(Movie):
based_on: str
Nun hat BookBasedMovie die Schlüssel name, year und based_on. Es ist äquivalent zu dieser Definition, da TypedDict-Typen strukturelle Kompatibilität verwenden
class BookBasedMovie(TypedDict):
name: str
year: int
based_on: str
Hier ist ein Beispiel für Mehrfachvererbung
class X(TypedDict):
x: int
class Y(TypedDict):
y: str
class XYZ(X, Y):
z: bool
Das TypedDict XYZ hat drei Einträge: x (Typ int), y (Typ str) und z (Typ bool).
Ein TypedDict kann nicht sowohl von einem TypedDict-Typ als auch von einer Nicht-TypedDict-Basisklasse erben.
Zusätzliche Hinweise zur TypedDict-Klassenvererbung
- Das Ändern eines Feldtyps einer übergeordneten TypedDict-Klasse in einer Unterklasse ist nicht erlaubt. Beispiel
class X(TypedDict): x: str class Y(X): x: int # Type check error: cannot overwrite TypedDict field "x"
Im oben skizzierten Beispiel geben TypedDict-Klassenannotationen für den Schlüssel
xden Typstrzurückprint(Y.__annotations__) # {'x': <class 'str'>}
- Mehrfachvererbung erlaubt keine widersprüchlichen Typen für dasselbe Feld.
class X(TypedDict): x: int class Y(TypedDict): x: str class XYZ(X, Y): # Type check error: cannot overwrite TypedDict field "x" while merging xyz: bool
Vollständigkeit (Totality)
Standardmäßig müssen alle Schlüssel in einem TypedDict vorhanden sein. Dies kann durch die Angabe der *Vollständigkeit* (totality) überschrieben werden. Hier ist, wie dies mit der klassenbasierten Syntax geschieht
class Movie(TypedDict, total=False):
name: str
year: int
Das bedeutet, dass ein Movie TypedDict beliebige der Schlüssel weggelassen haben kann. Daher sind diese gültig
m: Movie = {}
m2: Movie = {'year': 2015}
Ein Typüberprüfer soll nur ein literales False oder True als Wert für das total-Argument unterstützen. True ist der Standardwert und macht alle im Klassenkörper definierten Einträge erforderlich.
Das Vollständigkeits-Flag (totality flag) gilt nur für Einträge, die im Körper der TypedDict-Definition definiert sind. Vererbte Einträge werden nicht beeinflusst, sondern verwenden die Vollständigkeit des TypedDict-Typs, in dem sie definiert wurden. Dies ermöglicht es, eine Kombination aus erforderlichen und nicht erforderlichen Schlüsseln in einem einzigen TypedDict-Typ zu haben.
Alternative Syntax
Dieses PEP schlägt auch eine alternative Syntax vor, die auf ältere Python-Versionen wie 3.5 und 2.7 zurückportiert werden kann, die die in PEP 526 eingeführte Variablendefinitionssyntax nicht unterstützen. Sie ähnelt der traditionellen Syntax für die Definition von benannten Tupeln (named tuples)
Movie = TypedDict('Movie', {'name': str, 'year': int})
Es ist auch möglich, die Vollständigkeit mit der alternativen Syntax anzugeben
Movie = TypedDict('Movie',
{'name': str, 'year': int},
total=False)
Die Semantik ist äquivalent zur klassenbasierten Syntax. Diese Syntax unterstützt jedoch keine Vererbung, und es gibt keine Möglichkeit, sowohl erforderliche als auch nicht erforderliche Felder in einem einzelnen Typ zu haben. Die Motivation dafür ist, die abwärtskompatible Syntax so einfach wie möglich zu halten und gleichzeitig die häufigsten Anwendungsfälle abzudecken.
Von einem Typüberprüfer wird nur erwartet, dass er einen Wörterbuch-Display-Ausdruck als zweites Argument für TypedDict akzeptiert. Insbesondere muss eine Variable, die auf ein Wörterbuchobjekt verweist, nicht unterstützt werden, um die Implementierung zu vereinfachen.
Typsicherheit (Type Consistency)
Informell ausgedrückt, ist *Typsicherheit* (type consistency) eine Verallgemeinerung der is-subtype-of-Relation zur Unterstützung des Any-Typs. Sie ist formaler in PEP 483 definiert. Dieser Abschnitt führt die neuen, nicht-trivialen Regeln ein, die zur Unterstützung der Typsicherheit für TypedDict-Typen erforderlich sind.
Erstens ist jeder TypedDict-Typ mit Mapping[str, object] konsistent. Zweitens ist ein TypedDict-Typ A konsistent mit TypedDict B, wenn A strukturell kompatibel mit B ist. Dies ist genau dann wahr, wenn beide der folgenden Bedingungen erfüllt sind
- Für jeden Schlüssel in
BhatAden entsprechenden Schlüssel und der entsprechende Werttyp inAist konsistent mit dem Werttyp inB. Für jeden Schlüssel inBist der Werttyp inBauch konsistent mit dem entsprechenden Werttyp inA. - Für jeden erforderlichen Schlüssel in
Bist der entsprechende Schlüssel inAerforderlich. Für jeden nicht erforderlichen Schlüssel inBist der entsprechende Schlüssel inAnicht erforderlich.
Diskussion
- Werttypen verhalten sich invariant, da TypedDict-Objekte veränderbar sind. Dies ähnelt veränderbaren Container-Typen wie
ListundDict. Beispiel, wo dies relevant istclass A(TypedDict): x: Optional[int] class B(TypedDict): x: int def f(a: A) -> None: a['x'] = None b: B = {'x': 0} f(b) # Type check error: 'B' not compatible with 'A' b['x'] + 1 # Runtime error: None + 1
- Ein TypedDict-Typ mit einem erforderlichen Schlüssel ist nicht konsistent mit einem TypedDict-Typ, bei dem derselbe Schlüssel ein nicht erforderlicher Schlüssel ist, da letzterer das Löschen von Schlüsseln erlaubt. Beispiel, wo dies relevant ist
class A(TypedDict, total=False): x: int class B(TypedDict): x: int def f(a: A) -> None: del a['x'] b: B = {'x': 0} f(b) # Type check error: 'B' not compatible with 'A' b['x'] + 1 # Runtime KeyError: 'x'
- Ein TypedDict-Typ
Aohne Schlüssel'x'ist nicht konsistent mit einem TypedDict-Typ mit einem nicht erforderlichen Schlüssel'x', da zur Laufzeit der Schlüssel'x'vorhanden sein und einen inkompatiblen Typ haben könnte (der durchAaufgrund von strukturellem Subtyping möglicherweise nicht sichtbar ist). Beispielclass A(TypedDict, total=False): x: int y: int class B(TypedDict, total=False): x: int class C(TypedDict, total=False): x: int y: str def f(a: A) -> None: a['y'] = 1 def g(b: B) -> None: f(b) # Type check error: 'B' incompatible with 'A' c: C = {'x': 0, 'y': 'foo'} g(c) c['y'] + 'bar' # Runtime error: int + str
- Ein TypedDict ist nicht konsistent mit irgendeinem
Dict[...]-Typ, da Wörterbuchtypen destruktive Operationen erlauben, einschließlichclear(). Sie erlauben auch das Setzen beliebiger Schlüssel, was die Typsicherheit beeinträchtigen würde. Beispielclass A(TypedDict): x: int class B(A): y: str def f(d: Dict[str, int]) -> None: d['y'] = 0 def g(a: A) -> None: f(a) # Type check error: 'A' incompatible with Dict[str, int] b: B = {'x': 0, 'y': 'foo'} g(b) b['y'] + 'bar' # Runtime error: int + str
- Ein TypedDict mit allen
int-Werten ist nicht konsistent mitMapping[str, int], da es zusätzliche Nicht-int-Werte geben kann, die aufgrund von strukturellem Subtyping nicht über den Typ sichtbar sind. Diese können über die Methodenvalues()unditems()inMappingabgerufen werden, zum Beispiel. Beispielclass A(TypedDict): x: int class B(TypedDict): x: int y: str def sum_values(m: Mapping[str, int]) -> int: n = 0 for v in m.values(): n += v # Runtime error return n def f(a: A) -> None: sum_values(a) # Error: 'A' incompatible with Mapping[str, int] b: B = {'x': 0, 'y': 'foo'} f(b)
Unterstützte und nicht unterstützte Operationen
Typüberprüfer sollten eingeschränkte Formen der meisten dict-Operationen auf TypedDict-Objekten unterstützen. Das Leitprinzip ist, dass Operationen, die keine Any-Typen beinhalten, von Typüberprüfern abgelehnt werden sollten, wenn sie die Typsicherheit zur Laufzeit verletzen könnten. Hier sind einige der wichtigsten Verletzungen der Typsicherheit, die verhindert werden sollen
- Ein erforderlicher Schlüssel fehlt.
- Ein Wert hat einen ungültigen Typ.
- Ein Schlüssel, der nicht im TypedDict-Typ definiert ist, wird hinzugefügt.
Ein Schlüssel, der kein Literal ist, sollte generell abgelehnt werden, da sein Wert während der Typüberprüfung unbekannt ist und somit einige der oben genannten Verletzungen verursachen kann. (Verwendung von `final`-Werten und Literal-Typen verallgemeinert dies, um `final`-Namen und Literal-Typen abzudecken.)
Die Verwendung eines Schlüssels, von dem bekannt ist, dass er nicht existiert, sollte als Fehler gemeldet werden, auch wenn dies nicht unbedingt zu einem Laufzeit-Typfehler führt. Dies sind oft Fehler, und sie können Werte mit ungültigem Typ einfügen, wenn strukturelles Subtyping die Typen bestimmter Einträge verbirgt. Zum Beispiel sollte d['x'] = 1 einen Typüberprüfungsfehler erzeugen, wenn 'x' kein gültiger Schlüssel für d ist (was als TypedDict-Typ angenommen wird).
Zusätzliche Schlüssel, die bei der Erstellung eines TypedDict-Objekts enthalten sind, sollten ebenfalls abgefangen werden. In diesem Beispiel ist der Schlüssel director nicht in Movie definiert und es wird erwartet, dass dies einen Fehler von einem Typüberprüfer verursacht
m: Movie = dict(
name='Alien',
year=1979,
director='Ridley Scott') # error: Unexpected key 'director'
Typüberprüfer sollten die folgenden Operationen auf TypedDict-Objekten als unsicher ablehnen, obwohl sie für normale Wörterbücher gültig sind
- Operationen mit beliebigen
str-Schlüsseln (anstelle von String-Literalen oder anderen Ausdrücken mit bekannten String-Werten) sollten generell abgelehnt werden. Dies betrifft sowohl destruktive Operationen wie das Setzen eines Eintrags als auch schreibgeschützte Operationen wie Abonnement-Ausdrücke. Als Ausnahme von der obigen Regel solltend.get(e)unde in dfür TypedDict-Objekte erlaubt sein, für einen beliebigen Ausdruckemit dem Typstr. Die Motivation ist, dass diese sicher sind und nützlich für die Introspektion von TypedDict-Objekten sein können. Der statische Typ vond.get(e)sollteobjectsein, wenn der String-Wert vonestatisch nicht ermittelt werden kann. clear()ist nicht sicher, da es erforderliche Schlüssel entfernen könnte, von denen einige aufgrund von strukturellem Subtyping nicht direkt sichtbar sind.popitem()ist ähnlich unsicher, selbst wenn alle bekannten Schlüssel nicht erforderlich sind (total=False).del obj['key']sollte abgelehnt werden, es sei denn,'key'ist ein nicht erforderlicher Schlüssel.
Typüberprüfer dürfen das Lesen eines Eintrags mit d['x'] erlauben, auch wenn der Schlüssel 'x' nicht erforderlich ist, anstatt die Verwendung von d.get('x') oder eine explizite 'x' in d-Prüfung zu verlangen. Die Begründung ist, dass die Verfolgung der Existenz von Schlüsseln im Allgemeinen schwer zu implementieren ist und dass das Verhindern dies erfordern könnte, viele Änderungen am bestehenden Code vorzunehmen.
Die genauen Regeln für die Typüberprüfung obliegen jedem Typüberprüfer. In einigen Fällen können potenziell unsichere Operationen akzeptiert werden, wenn die Alternative darin besteht, falsche positive Fehler für idiomatischen Code zu erzeugen.
Verwendung von `final`-Werten und Literal-Typen
Typüberprüfer sollten `final`-Namen (PEP 591) mit String-Werten anstelle von String-Literalen in Operationen auf TypedDict-Objekten zulassen. Dies ist zum Beispiel gültig
YEAR: Final = 'year'
m: Movie = {'name': 'Alien', 'year': 1979}
years_since_epoch = m[YEAR] - 1970
Ebenso kann ein Ausdruck mit einem geeigneten Literal-Typ (PEP 586) anstelle eines Literal-Werts verwendet werden
def get_value(movie: Movie,
key: Literal['year', 'name']) -> Union[int, str]:
return movie[key]
Typüberprüfer sollen nur tatsächliche String-Literale unterstützen, keine `final`-Namen oder Literal-Typen, um Schlüssel in einer TypedDict-Typdefinition anzugeben. Außerdem kann nur ein boolesches Literal verwendet werden, um die Vollständigkeit in einer TypedDict-Definition anzugeben. Die Motivation dafür ist, Typdeklarationen eigenständig zu machen und die Implementierung von Typüberprüfern zu vereinfachen.
Abwärtskompatibilität
Um die Abwärtskompatibilität zu erhalten, sollten Typüberprüfer einen TypedDict-Typ nicht ableiten, es sei denn, es ist ausreichend klar, dass dies vom Programmierer gewünscht ist. Bei Unsicherheit sollte ein gewöhnlicher Wörterbuchtyp abgeleitet werden. Andernfalls kann bestehender Code, der ohne Fehler typisiert wird, Fehler generieren, sobald TypedDict-Unterstützung zum Typüberprüfer hinzugefügt wird, da TypedDict-Typen restriktiver sind als Wörterbuchtypen. Insbesondere sind sie keine Subtypen von Wörterbuchtypen.
Referenzimplementierung
Der mypy [2] Typüberprüfer unterstützt TypedDict-Typen. Eine Referenzimplementierung der Laufzeitkomponente ist im Modul typing_extensions [3] enthalten. Die ursprüngliche Implementierung war im Modul mypy_extensions [4] enthalten.
Abgelehnte Alternativen
Mehrere vorgeschlagene Ideen wurden verworfen. Die aktuellen Features scheinen viel abzudecken, und es war nicht klar, welche der vorgeschlagenen Erweiterungen mehr als marginal nützlich wären. Dieses PEP definiert ein Basis-Feature, das potenziell später erweitert werden kann.
Diese werden prinzipiell abgelehnt, da sie mit dem Geist dieses Vorschlags unvereinbar sind
- TypedDict ist nicht erweiterbar und behandelt nur einen spezifischen Anwendungsfall. TypedDict-Objekte sind zur Laufzeit normale Wörterbücher, und TypedDict kann nicht mit anderen wörterbuchähnlichen oder Mapping-ähnlichen Klassen verwendet werden, einschließlich Unterklassen von
dict. Es gibt keine Möglichkeit, TypedDict-Typen Methoden hinzuzufügen. Die Motivation hierfür ist Einfachheit. - TypedDict-Typdefinitionen könnten plausibel zur Laufzeit-Typüberprüfung von Wörterbüchern verwendet werden. Sie könnten zum Beispiel verwendet werden, um zu validieren, dass ein JSON-Objekt dem von einem TypedDict-Typ spezifizierten Schema entspricht. Dieses PEP enthält keine solche Funktionalität, da der Fokus dieses Vorschlags ausschließlich auf der statischen Typüberprüfung liegt und andere vorhandene Typen dies nicht unterstützen, wie in Klassenbasierte Syntax diskutiert. Eine solche Funktionalität kann beispielsweise von einer Drittanbieterbibliothek mithilfe des Moduls
typing_inspect[5] bereitgestellt werden. - TypedDict-Typen können nicht in
isinstance()- oderissubclass()-Prüfungen verwendet werden. Die Begründung ist ähnlich der, warum Laufzeit-Typüberprüfungen generell nicht mit vielen Typ-Hinweisen unterstützt werden.
Diese Features wurden aus diesem PEP weggelassen, sind aber potenzielle Erweiterungen, die in Zukunft hinzugefügt werden können
- TypedDict unterstützt nicht die Angabe eines *Standardwerttyps* für Schlüssel, die nicht explizit definiert sind. Dies würde die Verwendung beliebiger Schlüssel mit einem TypedDict-Objekt ermöglichen, und nur explizit aufgezählte Schlüssel würden eine spezielle Behandlung im Vergleich zu einem normalen, uniformen Wörterbuchtyp erhalten.
- Es gibt keine Möglichkeit, einzeln anzugeben, ob jeder Schlüssel erforderlich ist oder nicht. Keine vorgeschlagene Syntax war klar genug, und wir gehen davon aus, dass hierfür nur begrenzter Bedarf besteht.
- TypedDict kann nicht zur Angabe des Typs eines
**kwargs-Arguments verwendet werden. Dies würde die Einschränkung der zulässigen Schlüsselwortargumente und ihrer Typen ermöglichen. Gemäß PEP 484 bedeutet die Verwendung eines TypedDict-Typs als Typ von**kwargs, dass das TypedDict als *Wert* von beliebigen Schlüsselwortargumenten gültig ist, aber es schränkt nicht ein, welche Schlüsselwortargumente zulässig sein sollen. Die Syntax**kwargs: Expand[T]wurde dafür vorgeschlagen [6].
Danksagungen
David Foster trug die initiale Implementierung von TypedDict-Typen zu mypy bei. Verbesserungen an der Implementierung wurden von mindestens dem Autor (Jukka Lehtosalo), Ivan Levkivskyi, Gareth T, Michael Lee, Dominik Miedzinski, Roy Williams und Max Moroz beigesteuert.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0589.rst
Zuletzt geändert: 2025-04-09 22:14:53 GMT