Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

Inhaltsverzeichnis

Wichtig

Dieses PEP ist ein historisches Dokument: siehe Typed dictionaries und typing.TypedDict für aktuelle Spezifikationen und Dokumentation. Kanonische Typing-Spezifikationen werden auf der Website der Typing-Spezifikationen gepflegt; das Laufzeit-Typing-Verhalten wird in der CPython-Dokumentation beschrieben.

×

Siehe den Prozess zur Aktualisierung der Typ-Spezifikation, um Änderungen an der Typ-Spezifikation vorzuschlagen.

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_type enthalten, 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 ein total-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 dict ist (es ist niemals eine Unterklasse von dict).
  • 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 x den Typ str zurück

    print(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 B hat A den entsprechenden Schlüssel und der entsprechende Werttyp in A ist konsistent mit dem Werttyp in B. Für jeden Schlüssel in B ist der Werttyp in B auch konsistent mit dem entsprechenden Werttyp in A.
  • Für jeden erforderlichen Schlüssel in B ist der entsprechende Schlüssel in A erforderlich. Für jeden nicht erforderlichen Schlüssel in B ist der entsprechende Schlüssel in A nicht erforderlich.

Diskussion

  • Werttypen verhalten sich invariant, da TypedDict-Objekte veränderbar sind. Dies ähnelt veränderbaren Container-Typen wie List und Dict. Beispiel, wo dies relevant ist
    class 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 A ohne 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 durch A aufgrund von strukturellem Subtyping möglicherweise nicht sichtbar ist). Beispiel
    class 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ßlich clear(). Sie erlauben auch das Setzen beliebiger Schlüssel, was die Typsicherheit beeinträchtigen würde. Beispiel
    class 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 mit Mapping[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 Methoden values() und items() in Mapping abgerufen werden, zum Beispiel. Beispiel
    class 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

  1. Ein erforderlicher Schlüssel fehlt.
  2. Ein Wert hat einen ungültigen Typ.
  3. 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 sollten d.get(e) und e in d für TypedDict-Objekte erlaubt sein, für einen beliebigen Ausdruck e mit dem Typ str. Die Motivation ist, dass diese sicher sind und nützlich für die Introspektion von TypedDict-Objekten sein können. Der statische Typ von d.get(e) sollte object sein, wenn der String-Wert von e statisch 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()- oder issubclass()-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


Quelle: https://github.com/python/peps/blob/main/peps/pep-0589.rst

Zuletzt geändert: 2025-04-09 22:14:53 GMT