Important
This PEP is a historical document: seeTyped dictionaries andtyping.TypedDict for up-to-date specs and documentation. Canonical typing specs are maintained at thetyping specs site; runtime typing behaviour is described in the CPython documentation.
×
See thetyping specification update process for how to propose changes to the typing spec.
PEP 484 defines the typeDict[K,V] for uniformdictionaries, where each value has the same type, and arbitrary keyvalues are supported. It doesn’t properly support the common patternwhere the type of a dictionary value depends on the string value ofthe key. This PEP proposes a type constructortyping.TypedDict tosupport the use case where a dictionary object has a specific set ofstring keys, each with a value of a specific type.
Here is an example wherePEP 484 doesn’t allow us to annotatesatisfactorily:
movie={'name':'Blade Runner','year':1982}
This PEP proposes the addition of a new type constructor, calledTypedDict, to allow the type ofmovie to be representedprecisely:
fromtypingimportTypedDictclassMovie(TypedDict):name:stryear:int
Now a type checker should accept this code:
movie:Movie={'name':'Blade Runner','year':1982}
Representing an object or structured data using (potentially nested)dictionaries with string keys (instead of a user-defined class) is acommon pattern in Python programs. Representing JSON objects isperhaps the canonical use case, and this is popular enough that Pythonships with a JSON library. This PEP proposes a way to allow such codeto be type checked more effectively.
More generally, representing pure data objects using only Pythonprimitive types such as dictionaries, strings and lists has hadcertain appeal. They are easy to serialize and deserialize evenwhen not using JSON. They trivially support various useful operationswith no extra effort, including pretty-printing (throughstr() andthepprint module), iteration, and equality comparisons.
PEP 484 doesn’t properly support the use cases mentioned above. Let’sconsider a dictionary object that has exactly two valid string keys,'name' with value typestr, and'year' with value typeint. ThePEP 484 typeDict[str,Any] would be suitable, butit is too lenient, as arbitrary string keys can be used, and arbitraryvalues are valid. Similarly,Dict[str,Union[str,int]] is toogeneral, as the value for key'name' could be anint, andarbitrary string keys are allowed. Also, the type of a subscriptionexpression such asd['name'] (assumingd to be a dictionary ofthis type) would beUnion[str,int], which is too wide.
Dataclasses are a more recent alternative to solve this use case, butthere is still a lot of existing code that was written beforedataclasses became available, especially in large existing codebaseswhere type hinting and checking has proven to be helpful. Unlikedictionary objects, dataclasses don’t directly support JSONserialization, though there is a third-party package that implementsit[1].
A TypedDict type represents dictionary objects with a specific set ofstring keys, and with specific value types for each valid key. Eachstring key can be either required (it must be present) ornon-required (it doesn’t need to exist).
This PEP proposes two ways of defining TypedDict types. The first usesa class-based syntax. The second is an alternativeassignment-based syntax that is provided for backwards compatibility,to allow the feature to be backported to older Python versions. Therationale is similar to whyPEP 484 supports a comment-basedannotation syntax for Python 2.7: type hinting is particularly usefulfor large existing codebases, and these often need to run on olderPython versions. The two syntax options parallel the syntax variantssupported bytyping.NamedTuple. Other proposed features includeTypedDict inheritance and totality (specifying whether keys arerequired or not).
This PEP also provides a sketch of how a type checker is expectedto support type checking operations involving TypedDict objects.Similar toPEP 484, this discussion is left somewhat vague on purpose,to allow experimentation with a wide variety of different typechecking approaches. In particular, type compatibility should bebased on structural compatibility: a more specific TypedDict type canbe compatible with a smaller (more general) TypedDict type.
A TypedDict type can be defined using the class definition syntax withtyping.TypedDict as the sole base class:
fromtypingimportTypedDictclassMovie(TypedDict):name:stryear:int
Movie is a TypedDict type with two items:'name' (with typestr) and'year' (with typeint).
A type checker should validate that the body of a class-basedTypedDict definition conforms to the following rules:
key:value_type, optionally preceded by a docstring. Thesyntax for item definitions is identical to attribute annotations,but there must be no initializer, and the key name actually refersto the string value of the key instead of an attribute name.NamedTuple syntax. (Note thatit would not be sufficient to support type comments for backwardscompatibility with Python 2.7, since the class definition may have atotal keyword argument, as discussed below, and this isn’t validsyntax in Python 2.7.) Instead, this PEP provides an alternative,assignment-based syntax for backwards compatibility, discussed inAlternative Syntax.dict (it is never a subclass ofdict).An empty TypedDict can be created by only includingpass in thebody (if there is a docstring,pass can be omitted):
classEmptyDict(TypedDict):pass
Here is an example of how the typeMovie can be used:
movie:Movie={'name':'Blade Runner','year':1982}
An explicitMovie type annotation is generally needed, asotherwise an ordinary dictionary type could be assumed by a typechecker, for backwards compatibility. When a type checker can inferthat a constructed dictionary object should be a TypedDict, anexplicit annotation can be omitted. A typical example is a dictionaryobject as a function argument. In this example, a type checker isexpected to infer that the dictionary argument should be understood asa TypedDict:
defrecord_movie(movie:Movie)->None:...record_movie({'name':'Blade Runner','year':1982})
Another example where a type checker should treat a dictionary displayas a TypedDict is in an assignment to a variable with a previouslydeclared TypedDict type:
movie:Movie...movie={'name':'Blade Runner','year':1982}
Operations onmovie can be checked by a static type checker:
movie['director']='Ridley Scott'# Error: invalid key 'director'movie['year']='1982'# Error: invalid value type ("int" expected)
The code below should be rejected, since'title' is not a validkey, and the'name' key is missing:
movie2:Movie={'title':'Blade Runner','year':1982}
The created TypedDict type object is not a real class object. Hereare the only uses of the type a type checker is expected to allow:
m=Movie(name='Blade Runner',year=1982)
When called, the TypedDict type object returns an ordinarydictionary object at runtime:
print(type(m))# <class 'dict'>
In particular, TypedDict type objects cannot be used inisinstance() tests such asisinstance(d,Movie). The reason isthat there is no existing support for checking types of dictionaryitem values, sinceisinstance() does not work with manyPEP 484types, including common ones likeList[str]. This would be neededfor cases like this:
classStrings(TypedDict):items:List[str]print(isinstance({'items':[1]},Strings))# Should be Falseprint(isinstance({'items':['x']},Strings))# Should be True
The above use case is not supported. This is consistent with howisinstance() is not supported forList[str].
It is possible for a TypedDict type to inherit from one or moreTypedDict types using the class-based syntax. In this case theTypedDict base class should not be included. Example:
classBookBasedMovie(Movie):based_on:str
NowBookBasedMovie has keysname,year, andbased_on.It is equivalent to this definition, since TypedDict types usestructural compatibility:
classBookBasedMovie(TypedDict):name:stryear:intbased_on:str
Here is an example of multiple inheritance:
classX(TypedDict):x:intclassY(TypedDict):y:strclassXYZ(X,Y):z:bool
The TypedDictXYZ has three items:x (typeint),y(typestr), andz (typebool).
A TypedDict cannot inherit from both a TypedDict type and anon-TypedDict base class.
Additional notes on TypedDict class inheritance:
classX(TypedDict):x:strclassY(X):x:int# Type check error: cannot overwrite TypedDict field "x"
In the example outlined above TypedDict class annotations returnstypestr for keyx:
print(Y.__annotations__)# {'x': <class 'str'>}
classX(TypedDict):x:intclassY(TypedDict):x:strclassXYZ(X,Y):# Type check error: cannot overwrite TypedDict field "x" while mergingxyz:bool
By default, all keys must be present in a TypedDict. It is possibleto override this by specifyingtotality. Here is how to do thisusing the class-based syntax:
classMovie(TypedDict,total=False):name:stryear:int
This means that aMovie TypedDict can have any of the keys omitted. Thusthese are valid:
m:Movie={}m2:Movie={'year':2015}
A type checker is only expected to support a literalFalse orTrue as the value of thetotal argument.True is thedefault, and makes all items defined in the class body be required.
The totality flag only applies to items defined in the body of theTypedDict definition. Inherited items won’t be affected, and insteaduse totality of the TypedDict type where they were defined. This makesit possible to have a combination of required and non-required keys ina single TypedDict type.
This PEP also proposes an alternative syntax that can be backported toolder Python versions such as 3.5 and 2.7 that don’t support thevariable definition syntax introduced inPEP 526. Itresembles the traditional syntax for defining named tuples:
Movie=TypedDict('Movie',{'name':str,'year':int})
It is also possible to specify totality using the alternative syntax:
Movie=TypedDict('Movie',{'name':str,'year':int},total=False)
The semantics are equivalent to the class-based syntax. This syntaxdoesn’t support inheritance, however, and there is no way tohave both required and non-required fields in a single type. Themotivation for this is keeping the backwards compatible syntax assimple as possible while covering the most common use cases.
A type checker is only expected to accept a dictionary display expressionas the second argument toTypedDict. In particular, a variable thatrefers to a dictionary object does not need to be supported, to simplifyimplementation.
Informally speaking,type consistency is a generalization of theis-subtype-of relation to support theAny type. It is definedmore formally inPEP 483. This section introduces thenew, non-trivial rules needed to support type consistency forTypedDict types.
First, any TypedDict type is consistent withMapping[str,object].Second, a TypedDict typeA is consistent with TypedDictB ifA is structurally compatible withB. This is true if and onlyif both of these conditions are satisfied:
B,A has the corresponding key and thecorresponding value type inA is consistent with the value typeinB. For each key inB, the value type inB is alsoconsistent with the corresponding value type inA.B, the corresponding key is requiredinA. For each non-required key inB, the corresponding keyis not required inA.Discussion:
List andDict. Example where this is relevant:classA(TypedDict):x:Optional[int]classB(TypedDict):x:intdeff(a:A)->None:a['x']=Noneb:B={'x':0}f(b)# Type check error: 'B' not compatible with 'A'b['x']+1# Runtime error: None + 1
classA(TypedDict,total=False):x:intclassB(TypedDict):x:intdeff(a:A)->None:dela['x']b:B={'x':0}f(b)# Type check error: 'B' not compatible with 'A'b['x']+1# Runtime KeyError: 'x'
A with no key'x' is not consistent with aTypedDict type with a non-required key'x', since at runtimethe key'x' could be present and have an incompatible type(which may not be visible throughA due to structural subtyping).Example:classA(TypedDict,total=False):x:inty:intclassB(TypedDict,total=False):x:intclassC(TypedDict,total=False):x:inty:strdeff(a:A)->None:a['y']=1defg(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
Dict[...] type, sincedictionary types allow destructive operations, includingclear(). They also allow arbitrary keys to be set, whichwould compromise type safety. Example:classA(TypedDict):x:intclassB(A):y:strdeff(d:Dict[str,int])->None:d['y']=0defg(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
int values is not consistent withMapping[str,int], since there may be additional non-intvalues not visible through the type, due to structural subtyping.These can be accessed using thevalues() anditems()methods inMapping, for example. Example:classA(TypedDict):x:intclassB(TypedDict):x:inty:strdefsum_values(m:Mapping[str,int])->int:n=0forvinm.values():n+=v# Runtime errorreturnndeff(a:A)->None:sum_values(a)# Error: 'A' incompatible with Mapping[str, int]b:B={'x':0,'y':'foo'}f(b)
Type checkers should support restricted forms of mostdictoperations on TypedDict objects. The guiding principle is thatoperations not involvingAny types should be rejected by typecheckers if they may violate runtime type safety. Here are some ofthe most important type safety violations to prevent:
A key that is not a literal should generally be rejected, since itsvalue is unknown during type checking, and thus can cause some of theabove violations. (Use of Final Values and Literal Typesgeneralizes this to cover final names and literal types.)
The use of a key that is not known to exist should be reported as anerror, even if this wouldn’t necessarily generate a runtime typeerror. These are often mistakes, and these may insert values with aninvalid type if structural subtyping hides the types of certain items.For example,d['x']=1 should generate a type check error if'x' is not a valid key ford (which is assumed to be aTypedDict type).
Extra keys included in TypedDict object construction should also becaught. In this example, thedirector key is not defined inMovie and is expected to generate an error from a type checker:
m:Movie=dict(name='Alien',year=1979,director='Ridley Scott')# error: Unexpected key 'director'
Type checkers should reject the following operations on TypedDictobjects as unsafe, even though they are valid for normal dictionaries:
str keys (instead of string literalsor other expressions with known string values) should generally berejected. This involves both destructive operations such as settingan item and read-only operations such as subscription expressions.As an exception to the above rule,d.get(e) andeindshould be allowed for TypedDict objects, for an arbitrary expressione with typestr. The motivation is that these are safe andcan be useful for introspecting TypedDict objects. The static typeofd.get(e) should beobject if the string value ofecannot be determined statically.clear() is not safe since it could remove required keys, some ofwhich may not be directly visible because of structuralsubtyping.popitem() is similarly unsafe, even if all knownkeys are not required (total=False).delobj['key'] should be rejected unless'key' is anon-required key.Type checkers may allow reading an item usingd['x'] even ifthe key'x' is not required, instead of requiring the use ofd.get('x') or an explicit'x'ind check. The rationale isthat tracking the existence of keys is difficult to implement in fullgenerality, and that disallowing this could require many changes toexisting code.
The exact type checking rules are up to each type checker to decide.In some cases potentially unsafe operations may be accepted if thealternative is to generate false positive errors for idiomatic code.
Type checkers should allow final names (PEP 591) withstring values to be used instead of string literals in operations onTypedDict objects. For example, this is valid:
YEAR:Final='year'm:Movie={'name':'Alien','year':1979}years_since_epoch=m[YEAR]-1970
Similarly, an expression with a suitable literal type(PEP 586) can be used instead of a literal value:
defget_value(movie:Movie,key:Literal['year','name'])->Union[int,str]:returnmovie[key]
Type checkers are only expected to support actual string literals, notfinal names or literal types, for specifying keys in a TypedDict typedefinition. Also, only a boolean literal can be used to specifytotality in a TypedDict definition. The motivation for this is tomake type declarations self-contained, and to simplify theimplementation of type checkers.
To retain backwards compatibility, type checkers should not infer aTypedDict type unless it is sufficiently clear that this is desired bythe programmer. When unsure, an ordinary dictionary type should beinferred. Otherwise existing code that type checks without errors maystart generating errors once TypedDict support is added to the typechecker, since TypedDict types are more restrictive than dictionarytypes. In particular, they aren’t subtypes of dictionary types.
The mypy[2] type checker supports TypedDict types. A referenceimplementation of the runtime component is provided in thetyping_extensions[3] module. The originalimplementation was in themypy_extensions[4]module.
Several proposed ideas were rejected. The current set of featuresseem to cover a lot of ground, and it was not clear which of theproposed extensions would be more than marginally useful. This PEPdefines a baseline feature that can be potentially extended later.
These are rejected on principle, as incompatible with the spirit ofthis proposal:
dict. There is no way to addmethods to TypedDict types. The motivation here is simplicity.typing_inspect[5] third-partymodule, for example.isinstance() orissubclass()checks. The reasoning is similar to why runtime type checks aren’tsupported in general with many type hints.These features were left out from this PEP, but they are potentialextensions to be added in the future:
**kwargsargument. This would allow restricting the allowed keywordarguments and their types. According toPEP 484, using a TypedDicttype as the type of**kwargs means that the TypedDict is validas thevalue of arbitrary keyword arguments, but it doesn’trestrict which keyword arguments should be allowed. The syntax**kwargs:Expand[T] has been proposed for this[6].David Foster contributed the initial implementation of TypedDict typesto mypy. Improvements to the implementation have been contributed byat least the author (Jukka Lehtosalo), Ivan Levkivskyi, Gareth T,Michael Lee, Dominik Miedzinski, Roy Williams and Max Moroz.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0589.rst
Last modified:2025-04-09 22:14:53 GMT