Annotations Best Practices — Python 3.10.2 documentation

In Python 3.9 and older, accessing the annotations dict of an object is much more complicated than in newer versions. The problem is a design flaw in these older versions of Python, specifically to do with class annotations.

Best practice for accessing the annotations dict of other objects–functions, other callables, and modules–is the same as best practice for 3.10, assuming you aren’t calling inspect.get_annotations(): you should use three-argument getattr() to access the object’s __annotations__ attribute.

Unfortunately, this isn’t best practice for classes. The problem is that, since __annotations__ is optional on classes, and because classes can inherit attributes from their base classes, accessing the __annotations__ attribute of a class may inadvertently return the annotations dict of a base class. As an example:

class Base:
    a: int = 3
    b: str = 'abc'

class Derived(Base):
    pass

print(Derived.__annotations__)

This will print the annotations dict from Base, not Derived.

Your code will have to have a separate code path if the object you’re examining is a class (isinstance(o, type)). In that case, best practice relies on an implementation detail of Python 3.9 and before: if a class has annotations defined, they are stored in the class’s __dict__ dictionary. Since the class may or may not have annotations defined, best practice is to call the get method on the class dict.

To put it all together, here is some sample code that safely accesses the __annotations__ attribute on an arbitrary object in Python 3.9 and before:

if isinstance(o, type):
    ann = o.__dict__.get('__annotations__', None)
else:
    ann = getattr(o, '__annotations__', None)

After running this code, ann should be either a dictionary or None. You’re encouraged to double-check the type of ann using isinstance() before further examination.

Note that some exotic or malformed type objects may not have a __dict__ attribute, so for extra safety you may also wish to use getattr() to access __dict__.