This class describes a tree. Each node points at its parent and its children, so its annotations name the class itself:
class Node:
parent: Node | None
children: list[Node]
print(Node.__annotations__)
On Python 3.12 this fails with NameError: name 'Node' is not defined.
Python evaluates the annotations while it builds the class, and Node does
not exist yet. The usual fix is quotes, parent: "Node | None".
On Python 3.14 the same code runs and prints the real types. Python now
evaluates annotations only when code reads them, for example through
__annotations__. By then the class exists. This is called deferred
evaluation.
The change matters most for tools that read annotations while the program runs: serializers, documentation generators and dependency-injection frameworks. mypy reads your source, so its checks do not change. Run the examples below on Python 3.14 or newer.
Ask for the format you can use
A name in an annotation can still be missing when the annotations are read,
for example a type imported only under if TYPE_CHECKING:. The new
annotationlib module lets you choose what to get back:
from annotationlib import Format, ForwardRef, get_annotations
def process(widget: Widget, count: int) -> None:
pass
try:
get_annotations(process)
except NameError as error:
print("VALUE:", error)
partial = get_annotations(process, format=Format.FORWARDREF)
assert isinstance(partial["widget"], ForwardRef)
assert partial["count"] is int
print("STRING:", get_annotations(process, format=Format.STRING))
Format.VALUE, the default, gives the real objects. It raisesNameErrorforWidget, because nothing defines it.Format.FORWARDREFgives the real object where it can, and aForwardRefplaceholder for each name it cannot find.Format.STRINGgives the annotations as text, such as"Widget", for display.
A documentation tool can show the STRING form. A validator that has to know
the real type can try FORWARDREF and report the names it could not resolve.
The future import still gives strings
from __future__ import annotations still works on 3.14. With it, Python
stores every annotation as a string, and get_annotations returns those strings
unless you pass eval_str=True:
from __future__ import annotations
from annotationlib import get_annotations
class Item:
pass
def consume(item: Item) -> None:
pass
assert consume.__annotations__ == {"item": "Item", "return": "None"}
assert get_annotations(consume, eval_str=True) == {"item": Item, "return": None}
If your library must also run on Python 3.12, you still need the quotes or the future import. On 3.12, Python evaluates annotations when it defines the function or class.
The first read is cached
Python evaluates the annotations on the first read of __annotations__ and
keeps the result. A later change to a name they use does not change them:
Alias = int
def read(value: Alias) -> None:
pass
Alias = str
assert read.__annotations__["value"] is str
Alias = float
assert read.__annotations__["value"] is str
The first read happened while Alias was str, so the annotation stays str.
Check your understanding
On Python 3.14, def handler(req: Request) -> None runs before anything defines Request. When do you get a NameError?
Show answer
Hide answer
Only when code reads the annotations' values. Defining the function
and calling it both work, because Python has not evaluated Request yet. The
error appears when code reads handler.__annotations__, calls
get_annotations(handler) or inspect.signature(handler), or when a framework
inspects the handler for you.
Going further
Reading annotations can run code, so the STRING and FORWARDREF formats are
not a safe way to inspect untrusted objects. inspect.signature also takes an
annotation_format argument. The annotationlib reference
and the annotations how-to
cover the rest, including how to read annotations from a class body before
the class exists.