March 14, 2025·4 min read·engineeringpythonannotationstypingprerelease

Python 3.14 alpha 6 tests deferred annotation evaluation

Python 3.14.0a6 makes the annotation evaluation model available for compatibility work. Delaying execution addresses forward references while preserving real Python values for runtime consumers.

A type annotation can refer to a class that does not exist yet when Python executes a function definition. That is easy for a static analyzer to understand, but it has historically been awkward for the interpreter. Python annotations are expressions, and evaluating an expression requires its names to exist. Libraries that inspect annotations at runtime make this more than a question about type-checker convenience.

The March 14 release of Python 3.14.0a6 gives library authors another developer preview of the solution. Deferred annotation evaluation is among the changes listed for the developing series. This is the sixth of seven planned alphas, with feature work still open. Its purpose includes testing new behavior and the release process, and the announcement advises against production use.

Why strings did not settle the problem

PEP 649, written by Larry Hastings, describes the conflict behind deferred evaluation. Under the original semantics, Python computes annotation expressions when it binds the annotated object. A reference to a later class can therefore raise NameError before ordinary application code begins using the function.

The earlier solution, specified in PEP 563, converts annotation expressions to strings. A future import lets a module select that behavior. Static analysis can work with those representations without asking the interpreter to resolve the referenced objects during the function definition.

Runtime consumers have a different requirement. A framework may inspect an annotation to decide how to validate an argument or expose a function through another interface. A string naming a class is not the class object. Resolving it later requires the right namespace and successful evaluation. That becomes difficult when wrappers combine annotations originating in different modules or when local variables are involved.

The problem is therefore about when expressions run and which environment supplies their names. Replacing all annotations with text avoids early evaluation, but transfers reconstruction work to the libraries that need actual values. The interpreter already understands lexical scope, which makes compiler-generated deferred computation a reasonable place to handle that work.

I value Python's ability to let libraries participate in language conventions. Annotations put pressure on that arrangement because both static tools and runtime libraries consume the same notation. The language needs a defined evaluation model that does not require every library author to reconstruct scope independently.

A function computes the annotations

PEP 649 introduces __annotate__ on objects that support annotations. The compiler places the annotation expressions in a separate function and attaches that function to the annotated object. Reading __annotations__ invokes the computation when needed and caches the resulting dictionary.

A small example expresses the forward-reference case without a future import:

def accept(value: Record) -> Record:
    return value

class Record:
    pass

assert accept.__annotations__["value"] is Record

The significant ordering is the annotation lookup after the class definition. Delaying the computation gives the module time to bind Record. The resulting dictionary contains the class object, so a runtime consumer receives a value it can use directly.

This example describes the ordinary value-producing behavior that the alpha release makes available for testing. It does not imply that an unresolved name becomes valid at every point during module execution. Reading annotations before the class exists can still demand a value that the module has not defined.

The proposal also specifies that deferred expressions use names as they exist when evaluation occurs. Rebinding a referenced global before the first lookup can therefore affect the result. After successful computation, subsequent access returns the cached annotation dictionary. Evaluation timing is an observable part of the model.

Library inspection becomes execution timing

For a runtime library, the moment it reads annotations now deserves explicit attention. A decorator that examines a function during its definition has a different view of module initialization from a framework that inspects it after import completes. Deferred evaluation cannot erase that ordering distinction, although it makes later inspection more useful.

PEP 649 identifies separate consumers, including static typing tools, runtime annotation users, wrappers, and documentation generators. They do not all need the same representation. Its design includes alternative formats so consumers can request useful information without pretending that every name already resolves to a concrete value.

That design also explains why the separate annotation function matters. It preserves a connection to the defining environment. Re-evaluating a string through an arbitrarily selected globals dictionary can lose that connection. Wrappers and inherited annotations are particularly demanding because their public representation can combine information with different origins.

The alpha is an appropriate point to exercise those cases. Passing an application's normal function-call tests says little about a decorator that reads metadata during import. Annotation consumers need examples where definitions occur in different orders and where metadata crosses a wrapper boundary. Those are semantic cases arising directly from the design.

A preview with a defined scope

The release announcement still allows features to change during development. It lists other work, including a configuration C API and a new interpreter implementation for selected compilers. Those are developing-series capabilities, not a promise that this alpha has reached the final compatibility contract.

My interest in this preview is the possibility of reducing library-specific work around an established language feature. Python's ecosystem is a large part of why I use it, and changes to shared conventions affect how reliably those libraries compose. Deferred annotations put expression evaluation back under a compiler-defined mechanism. For libraries that consume real annotation values, the practical change is a later computation tied to the defining scope, with the first lookup determining when that computation occurs.