I want to get all annotations, including properties annotations. what I get is only the annotations set for regular annotations:
from dataclasses import dataclass
@dataclass
class Test:
first: int = 1
@property
def second(self) -> int:
return 5
t = Test()
print(t.__annotations__)
# prints: {'first': <class 'int'>}
Is there a way to get 'second' as part of __annotations__? Maybe even to force it into __anotations__.
Using __annotations__
to get info on how to transform a dataclass into a json seems like a bad idea. It either locks you out of using transient attributes like InitVar
or ClassVar
, or leads to inconsistent and buggy behavior if you do use them. You should be using __dataclass_fields__
instead.
That being said, sometimes your use case is simple enough that it'll do, or you're locked into it by one of your 3rd party packages that you can't easily improve, so here is one way to extend a dataclass's __annotations__
with those that exist on its properties:
class Test:
var: int
@property
def x(self) -> int:
return self.var + 2
def __post_init__(self):
cls = type(self)
properties = [(x, getattr(cls, x)) for x in dir(cls) if type(getattr(cls, x)) == property]
for name, property_ in properties:
cls.__annotations__[name] = property_.fget.__annotations__["return"]
Creating an instance of Test
will now include the property as if it were a regular typed attribute:
>>> Test(1).__annotations__
{'var': <class 'int'>, 'x': <class 'int'>}
Just as a side note, the __post_init__
code is executed during the initialization of every instance, even though strictly speaking it only needs to be run once on class generation, because __annotations__
is defined on the class level. Since it's idempotent it won't break anything, but here is a more confusing but arguably cleaner application of the same code which also works on regular classes.