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