Skip to content

[Bug] Behavior of typing_extensions.get_type_hints differs from typing.get_type_hints. #597

Closed
@emcd

Description

@emcd

(Possibly related to #410, but filing this separate issue to confirm and to consider documentation improvements, if so.)

The following simple reproducer will result in an error on Python 3.13.3 (latest stable CPython as of this writing) and earlier versions:

from __future__ import annotations

import dataclasses as dcls
import inspect
# import typing as typ
import typing_extensions as typ


@dcls.dataclass
class Foo:

    y: int
    x: typ.ClassVar[ int ] = 1


print( Foo.__init__.__annotations__ )
print( inspect.get_annotations( Foo.__init__, eval_str = True ) )
print( typ.get_type_hints( Foo.__init__ ) )

The error is:

$ hatch run python bugs/dcls-classvar-hints.py
{'y': 'int', 'x': 'typ.ClassVar[int]', 'return': None}
{'y': <class 'int'>, 'x': typing.ClassVar[int], 'return': None}
Traceback (most recent call last):
  File "/home/me/src/somepkg/bugs/dcls-classvar-hints.py", line 17, in <module>
    print( typ.get_type_hints( Foo.__init__ ) )
  File "/home/me/.local/share/hatch/env/virtual/somepkg/QPsnosgX/dynadoc/lib/python3.10/site-packages/typing_extensions.py", line 1315, in get_type_hints
    hint = typing.get_type_hints(
  File "/usr/lib/python3.10/typing.py", line 1871, in get_type_hints
    value = _eval_type(value, globalns, localns)
  File "/usr/lib/python3.10/typing.py", line 327, in _eval_type
    return t._evaluate(globalns, localns, recursive_guard)
  File "/usr/lib/python3.10/typing.py", line 693, in _evaluate
    type_ = _type_check(
  File "/usr/lib/python3.10/typing.py", line 167, in _type_check
    raise TypeError(f"{arg} is not valid as type argument")
TypeError: typing.ClassVar[int] is not valid as type argument

Switching the import from typing_extensions to typing causes get_type_hints to work correctly (on both Python 3.10.12 and Python 3.13.3.):

$ hatch run python bugs/dcls-classvar-hints.py
{'y': 'int', 'return': None}
{'y': <class 'int'>, 'return': None}
{'y': <class 'int'>, 'return': <class 'NoneType'>}

(Note the absence of the ClassVar annotation. This suggests that we possibly have a toxic interaction with the dataclasses.dataclass decorator in the mix.)

Likewise, removing the from __future__ import annotations and using typing_extensions.get_type_hints also works correctly:

$ hatch run python bugs/dcls-classvar-hints.py
{'y': <class 'int'>, 'return': None}
{'y': <class 'int'>, 'return': None}
{'y': <class 'int'>, 'return': <class 'NoneType'>}

And, inspect.get_annotations works correctly in all cases. But, I would prefer to use get_type_hints as it automatically handles MRO traversal and annotation merging for classes. I have been using typing_extensions rather than typing with the understanding that typing_extensions is providing a forward-compatibility layer. The forward-compatibility assumption is violated in this case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions