PEP 764 – Inline Typed Dictionaries
- Autor:
- Victorien Plot <contact at vctrn.dev>
- Sponsor:
- Eric Traut <erictr at microsoft.com>
- Discussions-To:
- Discourse thread
- Status:
- Entwurf
- Typ:
- Standards Track
- Thema:
- Typisierung
- Erstellt:
- 25-Okt-2024
- Python-Version:
- 3.15
- Post-History:
- 29-Jan-2025
Zusammenfassung
PEP 589 definiert eine klassenbasierte und eine funktionale Syntax zum Erstellen typisierter Wörterbücher. In beiden Szenarien ist die Definition einer Klasse oder die Zuweisung zu einem Wert erforderlich. In einigen Situationen kann dies unnötigen Boilerplate-Code hinzufügen, insbesondere wenn das typisierte Wörterbuch nur einmal verwendet wird.
Dieses PEP schlägt die Hinzufügung einer neuen Inline-Syntax durch Indizierung des Typs TypedDict vor.
from typing import TypedDict
def get_movie() -> TypedDict[{'name': str, 'year': int}]:
return {
'name': 'Blade Runner',
'year': 1982,
}
Motivation
Python-Wörterbücher sind eine wesentliche Datenstruktur der Sprache. Oft werden sie verwendet, um strukturierte Daten in Funktionen zurückzugeben oder zu empfangen. Es kann jedoch mühsam werden, TypedDict-Klassen zu definieren.
- Ein typisiertes Wörterbuch benötigt einen Namen, der möglicherweise nicht relevant ist.
- Verschachtelte Wörterbücher erfordern mehr als eine Klassendefinition.
Ein einfaches Beispiel für eine Funktion, die verschachtelte strukturierte Daten zurückgibt
from typing import TypedDict
class ProductionCompany(TypedDict):
name: str
location: str
class Movie(TypedDict):
name: str
year: int
production: ProductionCompany
def get_movie() -> Movie:
return {
'name': 'Blade Runner',
'year': 1982,
'production': {
'name': 'Warner Bros.',
'location': 'California',
}
}
Begründung
Die neue Inline-Syntax kann verwendet werden, um diese Probleme zu lösen.
def get_movie() -> TypedDict[{'name': str, 'year': int, 'production': TypedDict[{'name': str, 'location': str}]}]:
...
Obwohl weniger nützlich (da die funktionale oder sogar die klassenbasierte Syntax verwendet werden kann), können Inline-typisierte Wörterbücher einer Variablen als Alias zugewiesen werden.
InlineTD = TypedDict[{'name': str}]
def get_movie() -> InlineTD:
...
Spezifikation
Die spezielle Form TypedDict wird subscriptable und akzeptiert ein einzelnes Typargument, das ein dict sein muss, was den gleichen Semantiken wie die funktionale Syntax folgt (die Wörterbuchschlüssel sind Zeichenfolgen, die die Feldnamen darstellen, und Werte sind gültige Annotationsausdrücke). Nur die durch Kommas getrennte Liste von key: value-Paaren innerhalb der geschweiften Klammernkonstruktion ({k: <type>}) ist zulässig und sollte direkt als Typargument angegeben werden (d. h. es ist nicht zulässig, eine Variable zu verwenden, der zuvor eine dict-Instanz zugewiesen wurde).
Inline typisierte Wörterbücher können als *anonym* bezeichnet werden, was bedeutet, dass sie keinen spezifischen Namen haben (siehe Abschnitt Laufzeitverhalten).
Es ist möglich, ein verschachteltes Inline-Wörterbuch zu definieren.
Movie = TypedDict[{'name': str, 'production': TypedDict[{'location': str}]}]
# Note that the following is invalid as per the updated `type_expression` grammar:
Movie = TypedDict[{'name': str, 'production': {'location': str}}]
Obwohl keine Klassenargumente wie total angegeben werden können, kann für einzelne Felder jeder Typqualifizierer verwendet werden.
Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}]
Inline typisierte Wörterbücher sind implizit *total*, was bedeutet, dass alle Schlüssel vorhanden sein müssen. Die Verwendung des Required-Typqualifizierers ist daher überflüssig.
Typvariablen sind in Inline-typisierten Wörterbüchern zulässig, vorausgesetzt, sie sind an einen äußeren Gültigkeitsbereich gebunden.
class C[T]:
inline_td: TypedDict[{'name': T}] # OK, `T` is scoped to the class `C`.
reveal_type(C[int]().inline_td['name']) # Revealed type is 'int'
def fn[T](arg: T) -> TypedDict[{'name': T}]: ... # OK: `T` is scoped to the function `fn`.
reveal_type(fn('a')['name']) # Revealed type is 'str'
type InlineTD[T] = TypedDict[{'name': T}] # OK, `T` is scoped to the type alias.
T = TypeVar('T')
InlineTD = TypedDict[{'name': T}] # OK, same as the previous type alias, but using the old-style syntax.
def func():
InlineTD = TypedDict[{'name': T}] # Not OK: `T` refers to a type variable that is not bound to the scope of `func`.
Inline typisierte Wörterbücher können erweitert werden.
InlineTD = TypedDict[{'a': int}]
class SubTD(InlineTD):
pass
Änderungen der Typisierungsspezifikation
Das Inline-typisierte Wörterbuch fügt eine neue Art von Typausdruck hinzu. Daher wird die Produktion type_expression aktualisiert, um die Inline-Syntax aufzunehmen.
new-type_expression ::=type_expression| <TypedDict> '[' '{' (string: ':'annotation_expression',')* '}' ']' (where string is any string literal)
Laufzeitverhalten
Das Erstellen eines Inline-typisierten Wörterbuchs führt zu einer neuen Klasse, sodass T1 und T2 vom gleichen Typ sind.
from typing import TypedDict
T1 = TypedDict('T1', {'a': int})
T2 = TypedDict[{'a': int}]
Da Inline-typisierte Wörterbücher *anonym* sein sollen, wird ihr __name__-Attribut auf den Zeichenfolgenliteral <inline TypedDict> gesetzt. In Zukunft könnte ein explizites Klassenattribut hinzugefügt werden, um sie von benannten Klassen zu unterscheiden.
Obwohl TypedDict als Klasse dokumentiert ist, ist die Art und Weise, wie sie definiert wird, ein Implementierungsdetail. Die Implementierung muss angepasst werden, damit TypedDict subscriptable gemacht werden kann.
Abwärtskompatibilität
Dieses PEP führt keine rückwärtskompatiblen Änderungen ein.
Sicherheitsimplikationen
Es sind keine bekannten Sicherheitsfolgen aus diesem PEP bekannt.
Wie man das lehrt
Die neue Inline-Syntax wird sowohl in der Dokumentation des Moduls typing als auch in der Typisierungsspezifikation dokumentiert.
Wenn komplexe Wörterbuchstrukturen verwendet werden, kann die Definition von allem auf einer einzigen Zeile die Lesbarkeit beeinträchtigen. Code-Formatierer können helfen, indem sie das Inline-Typ-Wörterbuch über mehrere Zeilen formatieren.
def edit_movie(
movie: TypedDict[{
'name': str,
'year': int,
'production': TypedDict[{
'location': str,
}],
}],
) -> None:
...
Referenzimplementierung
Mypy unterstützt eine ähnliche Syntax als experimentelle Funktion.
def test_values() -> {"int": int, "str": str}:
return {"int": 42, "str": "test"}
Die Unterstützung für dieses PEP wurde in diesem Pull Request hinzugefügt.
Pyright fügte die Unterstützung für die neue Syntax in Version 1.1.387 hinzu.
Laufzeitimplementierung
Die notwendigen Änderungen wurden zuerst in typing_extensions in diesem Pull Request implementiert.
Abgelehnte Ideen
Verwendung der funktionalen Syntax in Annotationen
Die alternative funktionale Syntax könnte direkt als Annotation verwendet werden.
def get_movie() -> TypedDict('Movie', {'title': str}): ...
Aufruf-Ausdrücke werden derzeit aus verschiedenen Gründen (teuer zu verarbeiten, ihre Auswertung ist nicht standardisiert) in einem solchen Kontext nicht unterstützt.
Dies würde auch einen Namen erfordern, der manchmal nicht relevant ist.
Verwendung von dict oder typing.Dict mit einem einzelnen Typargument
Wir könnten dict oder typing.Dict mit einem einzelnen Typargument wiederverwenden, um dasselbe Konzept auszudrücken.
def get_movie() -> dict[{'title': str}]: ...
Dies würde zwar die Notwendigkeit vermeiden, TypedDict aus typing zu importieren, diese Lösung hat jedoch mehrere Nachteile.
- Für Typ-Checker ist
dicteine normale Klasse mit zwei Typvariablen.dictmit einem einzelnen Typargument zu parametrisieren, würde eine spezielle Behandlung durch Typ-Checker erfordern, da es keine Möglichkeit gibt, Parametrisierungs-Overloads auszudrücken.TypedDictist hingegen bereits eine Spezialform. - Wenn zukünftige Arbeiten die Möglichkeiten von Inline-typisierten Wörterbüchern erweitern, müssen wir uns keine Sorgen um die Auswirkungen der gemeinsamen Nutzung des Symbols mit
dictmachen. typing.Dictwurde durch PEP 585 als veraltet markiert (obwohl keine Entfernung geplant ist). Es wäre verwirrend für Benutzer, es für ein neues Typisierungsmerkmal zu verwenden (und würde Änderungen an Code-Linting-Tools erfordern).
Verwendung eines einfachen Wörterbuchs
Anstatt die Klasse TypedDict zu indizieren, könnte ein einfaches Wörterbuch als Annotation verwendet werden.
def get_movie() -> {'title': str}: ...
Jedoch fügte PEP 584 Union-Operatoren für Wörterbücher hinzu und PEP 604 führte Union-Typen ein. Beide Merkmale nutzen den Bitweise-ODER-Operator (|), was die folgenden Anwendungsfälle inkompatibel macht, insbesondere für die Laufzeit-Introspektion.
# Dictionaries are merged:
def fn() -> {'a': int} | {'b': str}: ...
# Raises a type error at runtime:
def fn() -> {'a': int} | int: ...
Erweiterung anderer typisierter Wörterbücher
Verschiedene Syntaxvarianten könnten verwendet werden, um die Fähigkeit zu haben, andere typisierte Wörterbücher zu erweitern.
InlineBase = TypedDict[{'a': int}]
Inline = TypedDict[InlineBase, {'b': int}]
# or, by providing a slice:
Inline = TypedDict[{'b': int} : (InlineBase,)]
Da Inline-typisierte Wörterbücher nur eine Teilmenge der bestehenden Syntax unterstützen sollen, ist die Hinzufügung dieses Erweiterungsmechanismus angesichts der zusätzlichen Komplexität nicht überzeugend genug, um unterstützt zu werden.
Wenn Schnittmengen in das Typsystem aufgenommen würden, könnte dies diesen Anwendungsfall abdecken.
Offene Fragen
Inline typisierte Wörterbücher und zusätzliche Elemente
PEP 728 führt das Konzept von *geschlossenen* Typ-Wörterbüchern ein. Wenn dieses PEP angenommen würde, wären Inline-typisierte Wörterbücher standardmäßig *geschlossen*. Dies bedeutet, dass PEP 728 zuerst behandelt werden muss, damit dieses PEP entsprechend aktualisiert werden kann.
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-0764.rst
Zuletzt geändert: 2025-05-06 22:54:17 GMT