Jonathan Herrera
Jonathan Herrera

Reputation: 6204

Python typing: Does TypedDict allow additional / extra keys?

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

Answers (3)

Jonathan Herrera
Jonathan Herrera

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

lei zhang
lei zhang

Reputation: 89

Python TypedDict does not support extra items yet and may be supported in python3.13 . See in PEP728

Upvotes: 7

hussic
hussic

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

Related Questions