Reputation: 6204
Does typing.TypedDict
allow extra keys? Does a value pass the typechecker, if it has keys which are not present on the definition of the TypedDict?
Upvotes: 18
Views: 10788
Reputation: 6204
It depends.
PEP-589, the specification of TypedDict
, explicitely forbids extra keys:
Extra keys included in TypedDict object construction should also be caught. In this example, the director key is not defined in Movie 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'
[emphasis by me]
The typecheckers mypy
, pyre
, and pyright
implement this according to the specification.
However, it is possible that a value with extra keys is accepted. This is because subtyping of TypedDicts is allowed, and the subtype might implement the extra key. PEP-589 only forbids extra keys in object construction, i.e. in literal assignment. As any value that complies with a subtype is always deemed to comply with the parent type and can be upcasted from the subtype to the parent type, an extra key can be introduced through a subtype:
from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
class MovieWithDirector(Movie):
director: str
# This is illegal:
movie: Movie = {
'name': 'Ash is purest white',
'year': 2018,
'director': 'Jia Zhangke',
}
# This is legal:
movie_with_director: MovieWithDirector = {
'name': 'Ash is purest white',
'year': 2018,
'director': 'Jia Zhangke',
}
# This is legal, MovieWithDirector is a subtype of Movie
movie: Movie = movie_with_director
In the example above, we see that the same value can sometimes be considered complying with Movie
by the typing system, and sometimes not.
As a consequence of subtyping, typing a parameter as a certain TypedDict is not a safeguard against extra keys, because they could have been introduced through a subtype.
If your code is sensitive with regard to the presence of extra keys (for instance, if it makes use of param.keys()
, param.values()
or len(param)
on the TypedDict
parameter param
), this could lead to problems when extra keys are present. A solution to this problem is to either handle the exceptional case that extra keys are actually present on the parameter or to make your code insensitive against extra keys.
If you want to test that your code is robust against extra keys, you cannot simply add a key in the test value:
def some_movie_function(movie: Movie):
# ...
def test_some_movie_function():
# this will not be accepted by the type checker:
result = some_movie_function({
'name': 'Ash is purest white',
'year': 2018,
'director': 'Jia Zhangke',
'genre': 'drama',
})
Workarounds are to either make the type checkers ignore the line or to create a subtype for your test, introducing the extra keys only for your test:
class ExtendedMovie(Movie):
director: str
genre: str
def test_some_movie_function():
extended_movie: ExtendedMovie = {
'name': 'Ash is purest white',
'year': 2018,
'director': 'Jia Zhangke',
'genre': 'drama',
}
result = some_movie_function(test_some_movie_function)
# run assertions against result
Upvotes: 17
Reputation: 89
Python TypedDict does not support extra items yet and may be supported in python3.13 . See in PEP728
Upvotes: 7
Reputation: 1920
If you know the optionals keys, you can use total=False
:
class Movie(TypedDict):
name: str
year: int
class MovieWithDirector(Movie, total=False):
director: str
x: MovieWithDirector = {'name':''} # error: missing "year"
y: MovieWithDirector = {'name':'', 'year':1} # ok
z: MovieWithDirector = {'name':'', 'year':1, 'director':''} # ok
w: MovieWithDirector = {'name':'', 'year':1, 'foo':''} # error key "foo"
Upvotes: 2