Skip to content

get_type_hints() fails on TypedDict inheriting from a generic TypedDict under PEP 563 #138949

Open

Description

@JelleZijlstra

Bug report

Bug description:

This fails:

from __future__ import annotations
import typing
class TD1[T](typing.TypedDict):
    a: T

class TD2(TD1):
    pass

print(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.)

CPython versions tested on:

CPython main branch, 3.15, 3.14, 3.13, 3.12

Operating systems tested on:

macOS

Linked PRs

Activity

  1. wiomoc commented on Sep 15, 2025

    @wiomoc
    Contributor

    I was able to reproduce this error in a smaller MRE - subclassing TD1 isn't even necessary:

    from __future__ import annotations
    import typing
    
    class TD1[T](typing.TypedDict):
        a: T
    
    
    print(typing.get_type_hints(TD1))
  2. JelleZijlstra commented on Sep 15, 2025

    @JelleZijlstra
    MemberAuthor

    @wiomoc that one works in 3.13 and 3.12, and I'll have a PR up soon fixing it for 3.14+.

  3. added 3 commits that reference this issue on Sep 15, 2025
    95a4187
    6d6aba2
    25ab548
  4. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Sep 17, 2025
  5. sobolevn commented on Jun 1, 2026

    @sobolevn
    Member

    The root cause of this bug is that __type_params__ of TD1 is not inherited by TD2:

    from __future__ import annotations
    
    from typing import get_type_hints, TypedDict
    
    class TD1[T](TypedDict):
        a: T
    
    class TD2(TD1):
        b: int
    
    print(TD1.__required_keys__)
    print(TD1.__type_params__)
    print(TD1.__annotations__)
    print('--')
    print(TD2.__required_keys__)
    print(TD2.__type_params__)
    print(TD2.__annotations__)
    print('--')

    Will produce:

    frozenset({'a'})
    (T,)
    {'a': ForwardRef('T', module='__main__', owner=<class '__main__.TD1'>)}
    --
    frozenset({'b', 'a'})
    ()
    {'a': ForwardRef('T', module='__main__', owner=<class '__main__.TD1'>), 'b': ForwardRef('int', module='__main__', owner=<class '__main__.TD2'>)}
    --
    

    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:

    1. 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?
    2. 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?

    Which way do you prefer? Any other ideas?

  6. JelleZijlstra commented on Jun 2, 2026

    @JelleZijlstra
    MemberAuthor

    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().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions