How can I access to __annotations__ of parent class?

Question:

Is there any way to access to the typing __annotations __ of the parent class?

In the above example, the class Student inherit from class Person, but It does not contains the typing annotations from the Person class.

class Person:
    name: str
    address: str

    def __init__(self):
        print(self.__annotations__)


class Student(Person):
    year: int


person = Person()
# {'name': <class 'str'>, 'address': <class 'str'>}

student = Student()
# {'year': <class 'int'>}
# HERE I would expect the name and the address props

Answers:

self.__annotations__, in the absence of an instance attribute named __annotations__, is equivalent to type(self).__annotations__. Since Student.__annotations__ is defined, there is no reason to look for Person.__annotations__. You would need to check each class in your MRO. The easiest way to do that is to define a single class method in some base class (or make it an external function that isn’t associated with any single class).

class Person:
    name: str
    address: str

    @classmethod
    def get_annotations(cls):
        d = {}
        for c in cls.mro():
            try:
                d.update(**c.__annotations__)
            except AttributeError:
                # object, at least, has no __annotations__ attribute.
                pass
        return d

    def __init__(self):
        print(self.get_annotations())


class Student(Person):
    year: int
Answered By: chepner

Here’s the answer I would have liked to have seen when a Google search brought me here.

from collections import ChainMap

def all_annotations(cls) -> ChainMap:
    """Returns a dictionary-like ChainMap that includes annotations for all 
       attributes defined in cls or inherited from superclasses."""
    return ChainMap(*(c.__annotations__ for c in cls.__mro__ if '__annotations__' in c.__dict__) )

Unlike @chepner’s approach, this correctly handles cases where multiple superclasses provide different annotations for the same attribute name (which is often inadvisable, but can be fine, e.g. when a subclass gives an attribute with a more specific annotation). @chepner’s approach will give priority to the annotation that occurs last in the method resolution order (MRO), whereas Python’s class inheritance generally gives priority to whichever class comes first in that order. (If you did want to use @chepner’s approach, you’d probably do better to update annotations in the reverse of the MRO, taking care not to accidentally lose track of any annotations defined in this class.)

Answered By: JustinFisher

Here’s a one-liner without additional imports:

def _get_annotated_attributes(cls) -> Set[str]:
    return {
        key
        for c in cls.mro() if hasattr(c, '__annotations__')
        for key in c.__annotations__
    }
Answered By: Polor Beer
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.