When defining a class/module with annotated fields, how can I get annotations as like in functions?
class Test:
def __init__(self):
self.x : int
t = Test()
Now I need 'int' from getattr(t,'x')
With baseline Python, there is no option to do what you want without changing the definition of Test
. The minimalist change would be to annotate the attribute at class level:
class Test:
x: int
def __init__(self):
# define self.x or not, but it needn't be annotated again
This is actually perfectly fine; by default, annotations at class scope are assumed to refer to instance attributes, not class attributes (assigning to a value at class scope creates a class attribute, but annotating it does not); you have to explicitly use typing.ClassVar
to indicate the annotated type is intended to be a class attribute only. PEP 526's section on class and instance variable annotations defines these behaviors; they're something you can rely on, not just an accident of implementation.
Once you've done this, typing.get_type_hints
will return {'x': int}
for both Test
and t
in your example case.
While that's enough on its own, I'll note that in many such cases nowadays, as long as you're annotating anyway, you can simplify your code with the dataclasses
module, getting the annotations and basic functionality defined for you with minimal typing. Simple replacement code for your case would be:
import dataclasses
@dataclasses.dataclass
class Test:
x: int
While your case doesn't showcase the full feature set (it's basically just replacing __init__
with the decorator), it's still doing more than meets the eye. In addition to defining __init__
for you (it expects to receive an x
argument which is annotated to be an int
), as well as a suitable __repr__
and __eq__
, you can define defaults easily (just assign the default at point of annotation or for more complex or mutable cases, assign a dataclasses.field
instead), and you can pass arguments to dataclass
to make it produce sortable or immutable instances.
In your case, the main advantage is removing redundancy; x
is annotated and referenced exactly once, rather than being annotated once at class level, then used (and optionally, annotated again) during initialization.
I am not sure you can get the annotations of self.x
easily.
Assuming your code:
class Test:
def __init__(self):
self.x: int = None
t = Test()
I tried looking for __annotations__
in Test
and t
(where I would expect it to be), without much luck.
However, what you could do is this workaround:
class Test:
x: int
def __init__(self):
# annotation from here seems to be unreachable from `__annotations__`
self.x: str
t = Test()
print(Test.__annotations__)
# {'x': <class 'int'>}
print(t.__annotations__)
# {'x': <class 'int'>}
If you want to be able to inspect the type of self.x
within mypy
check answer from @ruohola.
Note that mypy
(at least v.0.560) does get confused by annotating x
both from the class
and from the __init__
, i.e. it looks like the annotation of self.x
is boldly ignored:
import sys
class Test:
x: str = "0"
def __init__(self):
self.x: int = 1
t = Test()
print(Test.x, t.x)
# 0 1
print(Test.x is t.x)
# False
if "mypy" in sys.modules:
reveal_type(t.x)
# from mypyp: annotated_self.py:14: error: Revealed type is 'builtins.str'
reveal_type(Test.x)
# from mypy: annotated_self.py:15: error: Revealed type is 'builtins.str'
Test.x = 2
# from mypy: annotated_self.py:17: error: Incompatible types in assignment (expression has type "int", variable has type "str")
t.x = "3"
# no complaining from `mypy`
t.x = 4
# from mypy: annotated_self.py:19: error: Incompatible types in assignment (expression has type "int", variable has type "str")
print(Test.x, t.x)
# 2 4
If you're using mypy
, you can use reveal_type()
to check the type annotation of any expression. Note that this function is only usable when running mypy
, and not at normal runtime.
I also use typing.TYPE_CHECKING
, to not get an error when running the file normally, since this special constant is only assumed to be True
by 3rd party type checkers.
test.py
:
from typing import Dict, Optional, TYPE_CHECKING
class Test:
def __init__(self) -> None:
self.x: Optional[Dict[str, int]]
test = Test()
if TYPE_CHECKING:
reveal_type(test.x)
else:
print("not running with mypy")
Example when running mypy
on it:
$ mypy test.py
test.py:10: error: Revealed type is 'Union[builtins.dict[builtins.str, builtins.int], None]'
And when running it normally:
$ python3 test.py
not running with mypy
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With