How to inject class-variable annotations in Python 3.7+?

2.9k Views Asked by At

In Python 3.7, static fields can be annotated with the following syntax defined in PEP 526:

class A:
   foo: int

How can I make these annotations later on, after the class is defined? I would expect the following to work:

A.bar : float

However, it doesn't seem to have an identical effect to the first code. When we look at A's __dict___, those two snippets don't have the same effect.

After example 1 and after example 2, we get identical __dict__'s, i.e. the second example must show effect somewhere else. The dict created is:

>> pprint(A.__dict__):
mappingproxy({'__annotations__': {'foo': <class 'int'>}, # <-!
              '__dict__': <attribute '__dict__' of 'A' objects>,
              '__doc__': None,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'A' objects>})

I don't think editing __annotations__ is a "good" method of achieving what I want, especially because I'm not sure if it's the only case where foo is registered.

What is the proper way to do this?

1

There are 1 best solutions below

3
On BEST ANSWER

The information used in variable annotations for instance and class attributes is stored in the __annotations__ mapping on the class, a dictionary that's writable.

If you want to add to the information stored there, then just add your new information directly to that mapping:

A.__annotations__['bar'] = float

The A.bar: float annotation is discarded by Python, as there is no dedicated location to store the information for annotated expressions; it is up to the static type checker implementation to decide if that expression has meaning.

See the Runtime Effects of Type Annotations section of PEP 526 -- Syntax for Variable Annotations, the document that defines this syntax:

In addition, at the module or class level, if the item being annotated is a simple name, then it and the annotation will be stored in the __annotations__ attribute of that module or class (mangled if private) as an ordered mapping from names to evaluated annotations.

and from the Annotated assignment statements section of the Python reference documentation:

For simple names as assignment targets, if in class or module scope, the annotations are evaluated and stored in a special class or module attribute __annotations__ that is a dictionary mapping from variable names (mangled if private) to evaluated annotations. This attribute is writable and is automatically created at the start of class or module body execution, if annotations are found statically.

A.bar is not a simple name, it is an expression, so it is not stored; if you want to retain that information in the __annotations__ mapping for runtime access, then manually setting it is the only option.