Idea: Simpler and More Expressive Type Annotations

You can also get this one to fail on current Python if you also put in an annotation that will fail with a non-name error and force get_annotations into a fallback mode. (This also revealed a bug with conditional annotations and Format.STRING).

from annotationlib import get_annotations, Format

x = "global"
def outer(broken=False):
    x = "nonlocal"
    class Cls:
        global x
        ann: x
        ref: undefined
        if broken:
            break_annotation: object.does_not_exist  # force an AttributeError
    Cls.x = "class"
    return Cls

print(get_annotations(outer(False), format=Format.FORWARDREF)['ann'])  # global
print(get_annotations(outer(True), format=Format.FORWARDREF)['ann'])  # class

The ‘almost but not quite enough’ context is what’s being used here for the case where the annotations are broken by an attribute error. It’s also what I currently use for deferred annotations in reannotate, which is my current implementation of the deferred annotations I’ve mentioned before.


You need the namespace per annotation. Take the example of dataclasses __init__ that can contain annotations that come from different modules. If its __annotate__ function is to support the new format in a useful manner it needs to be able to provide the context for each annotation separately.

The DEFERRED format I’ve been speaking of is essentially designed for this. It’s a pairing of some arbitrary object - currently most commonly either an AST expression or a string - with an ‘EvaluationContext’ object required to evaluate it.

>>> from annotationlib import call_annotate_function, Format
>>> from reannotate import get_deferred_annotations, ReAnnotate
>>> class Example:
...     a: int
...     b: list[str]
...     c: unknown
... 
>>> annos = get_deferred_annotations(Example)
>>> print(annos)
{'a': DeferredAnnotation('int'), 'b': DeferredAnnotation('list[str]'), 'c': DeferredAnnotation('unknown')}
>>> annos['a']._obj  # String as it's just a name
'int'
>>> annos['b']._obj  # AST expression
Subscript(value=Name(id='list', ctx=Load()), slice=Name(id='str', ctx=Load()), ctx=Load())

Now, in its current form it’s not enough for expressions not supported by Format.STRING but that could be an internal change rather than a public format.