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