You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
get_type_hints() fails on TypedDict inheriting from a generic TypedDict under PEP 563 #138949
from __future__ importannotationsimporttypingclassTD1[T](typing.TypedDict):
a: TclassTD2(TD1):
passprint(typing.get_type_hints(TD2))
$ uv run --python=3.13 sample3.py
Traceback (most recent call last):
File "/Users/jelle/py/cpython/sample3.py", line 9, in <module>
print(typing.get_type_hints(TD2))
~~~~~~~~~~~~~~~~~~~~~^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/lib/python3.13/typing.py", line 2461, in get_type_hints
value = _eval_type(value, base_globals, base_locals, base.__type_params__)
File "/opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/lib/python3.13/typing.py", line 474, in _eval_type
return t._evaluate(globalns, localns, type_params, recursive_guard=recursive_guard)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/lib/python3.13/typing.py", line 1081, in _evaluate
eval(self.__forward_code__, globalns, localns),
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 1, in <module>
NameError: name 'T' is not defined
Also reproduces on 3.12 and on the latest RC of 3.14.
(Got here while investigating #137226 (comment), but since this variant is a longstanding bug we don't need to fix it for 3.14.0. I want to record and eventually fix it though.)
When we try to resolve the ForwardRef which is ForwardRef('T', module='__main__', owner=<class '__main__.TD1'>), but we don't have TD2.__type_params__ set. And we fail with a NameError as shown.
Possible options:
We can populate "child" typed dicts with type params from the base ones. It seems kinda expected, since we "inherit" all the keys? But, this can produce unknown side effects for libs like msgspec and pydantic, etc. This would also make TD2 implicitly generic, which is kinda strange?
We can create a new attribute called __parent_typed_dict_type_params__, where we can copy parameters from parent typed dicts. This feels like a hack, but it more safe in terms of generic, probably?
The root cause is that we made the questionable decision to copy over base class annotations into child TypedDict classes. Every annotation needs to be evaluated in the context of the class where it is defined, and the way TypedDict currently works at runtime makes that difficult. Unfortunately, it's probably too late to fix that completely. However, the bases are still accessible in __orig_bases__, so maybe we can reconstruct the right class to use in get_type_hints().
Activity
wiomoc commentedon Sep 15, 2025
I was able to reproduce this error in a smaller MRE - subclassing
TD1isn't even necessary:JelleZijlstra commentedon Sep 15, 2025
@wiomoc that one works in 3.13 and 3.12, and I'll have a PR up soon fixing it for 3.14+.
pythongh-137226: Fix get_type_hints() on generic TypedDict with strin…
gh-137226: Fix get_type_hints() on generic TypedDict with stringified…
[3.14] pythongh-137226: Fix get_type_hints() on generic TypedDict wit…
sobolevn commentedon Jun 1, 2026
The root cause of this bug is that
__type_params__ofTD1is not inherited byTD2:Will produce:
When we try to resolve the
ForwardRefwhich isForwardRef('T', module='__main__', owner=<class '__main__.TD1'>), but we don't haveTD2.__type_params__set. And we fail with aNameErroras shown.Possible options:
msgspecandpydantic, etc. This would also makeTD2implicitly generic, which is kinda strange?__parent_typed_dict_type_params__, where we can copy parameters from parent typed dicts. This feels like a hack, but it more safe in terms of generic, probably?Which way do you prefer? Any other ideas?
JelleZijlstra commentedon Jun 2, 2026
The root cause is that we made the questionable decision to copy over base class annotations into child TypedDict classes. Every annotation needs to be evaluated in the context of the class where it is defined, and the way TypedDict currently works at runtime makes that difficult. Unfortunately, it's probably too late to fix that completely. However, the bases are still accessible in
__orig_bases__, so maybe we can reconstruct the right class to use inget_type_hints().