3.7. Typing TypedDict

  • Since Python 3.8

  • PEP 589 -- TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys

  • PEP 655 –- Marking individual TypedDict items as required or potentially-missing

3.7.1. SetUp

>>> from typing import TypedDict

3.7.2. Problem

>>> a: dict[str,str] = {'firstname': 'Mark', 'lastname': 'Watney'}
>>> b: dict[str,str] = {'first_name': 'Mark', 'last_name': 'Watney'}

3.7.3. Solution

>>> class User(TypedDict):
...     firstname: str
...     lastname: str
>>>
>>>
>>> a: User = {'firstname': 'Mark', 'lastname': 'Watney'}    # ok
>>> b: User = {'first_name': 'Mark', 'last_name': 'Watney'}  # error
>>> c: User = User(firstname='Mark', lastname='Watney')

3.7.4. Optional Values

>>> class User(TypedDict):
...     firstname: str
...     lastname: str
...     age: int | None
>>>
>>>
>>> a: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': 41}    # ok
>>> b: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': None}  # ok
>>> c: User = {'firstname': 'Mark', 'lastname': 'Watney'}               # error, age is Required

3.7.5. NotRequired

  • Since Python 3.11

  • PEP 655 –- Marking individual TypedDict items as required or potentially-missing

>>> from typing import NotRequired
>>> class User(TypedDict):
...     firstname: str
...     lastname: str
...     age: NotRequired[int]
>>>
>>>
>>> a: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': 41}    # ok
>>> b: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': None}  # error, must be int
>>> c: User = {'firstname': 'Mark', 'lastname': 'Watney'}               # ok

3.7.6. Required

  • Since Python 3.11

  • PEP 655 –- Marking individual TypedDict items as required or potentially-missing

>>> from typing import Required, NotRequired
>>> class User(TypedDict):
...     firstname: Required[str]
...     lastname: Required[str]
...     age: NotRequired[int]
>>>
>>>
>>> a: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': 41}    # ok
>>> b: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': None}  # error, must be int
>>> c: User = {'firstname': 'Mark', 'lastname': 'Watney'}               # ok

3.7.7. Total

  • Since Python 3.11

  • PEP 655 –- Marking individual TypedDict items as required or potentially-missing

>>> from typing import Required, NotRequired
>>> class User(TypedDict, total=True):
...     firstname: str
...     lastname: str
...     age: int
>>>
>>>
>>> a: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': 41}    # ok
>>> c: User = {'firstname': 'Mark', 'lastname': 'Watney'}               # error, age is Requireds

3.7.8. Field Access

  • You can access fields by setitem, but not by attribute.

>>> class User(TypedDict):
...     firstname: str
...     lastname: str
>>>
>>>
>>> mark: User = User(firstname='Mark', lastname='Watney')
>>>
>>> mark['firstname']
'Mark'
>>>
>>> mark.firstname
Traceback (most recent call last):
AttributeError: 'dict' object has no attribute 'firstname'

3.7.9. Is Instance

  • NamedTuple is a subclass of tuple.

>>> class User(TypedDict):
...     firstname: str
...     lastname: str
>>>
>>>
>>> mark: User = User(firstname='Mark', lastname='Watney')
>>>
>>> isinstance(mark, User)
Traceback (most recent call last):
TypeError: TypedDict does not support instance and class checks
>>>
>>> isinstance(mark, dict)
True

3.7.10. Use Case - 1

>>> from datetime import datetime, date, time
>>> from typing import Literal, TypedDict
>>>
>>> class Log(TypedDict):
...     when: datetime
...     level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
...     message: str
>>>
>>>
>>> def parse(line: str) -> Log:
...     d, t, lvl, msg = line.strip().split(', ', maxsplit=3)
...     d = date.fromisoformat(d)
...     t = time.fromisoformat(t)
...     dt = datetime.combine(d,t)
...     return Log(when=dt, level=lvl, message=msg)
>>> line = '1969-07-21, 02:56:15, WARNING, Neil Armstrong first words on the Moon'
>>>
>>> parse(line)  
{'when': datetime.datetime(1969, 7, 21, 2, 56, 15),
 'level': 'WARNING',
 'message': 'Neil Armstrong first words on the Moon'}

3.7.11. Further Reading

3.7.12. References

3.7.13. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Typing Annotations Mapping
# - Difficulty: easy
# - Lines: 4
# - Minutes: 2

# %% English
# 1. Define `User: TypedDict` with:
#    - `firstname: str`
#    - `lastname: str`
#    - `age: int`
# 2. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj `User: TypedDict` z:
#    - `firstname: str`
#    - `lastname: str`
#    - `age: int`
# 2. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 5), \
'Python 3.5+ required'

>>> from typing import get_type_hints, is_typeddict
>>> assert is_typeddict(User), \
'Class `User` must be a `TypedDict`'

>>> get_type_hints(User)
{'firstname': <class 'str'>, 'lastname': <class 'str'>, 'age': <class 'int'>}

>>> data: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': 41}
>>> data
{'firstname': 'Mark', 'lastname': 'Watney', 'age': 41}

>>> data: User = User(**{'firstname': 'Mark', 'lastname': 'Watney', 'age': 41})
>>> data
{'firstname': 'Mark', 'lastname': 'Watney', 'age': 41}

>>> data: User = User(firstname='Mark', lastname='Watney', age=41)
>>> data
{'firstname': 'Mark', 'lastname': 'Watney', 'age': 41}
"""

from typing import TypedDict

# Define `User: TypedDict` with:
# - `firstname: str`
# - `lastname: str`
# - `age: int`
class User:
    ...