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
More information in Type Annotations
More information in CI/CD Type Checking
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:
...