Search code examples
pythonooppython-dataclassespython-class

python __annotations__ from @property


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__.


Solution

  • 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.