PEP 849: More Expressive Type Expressions
The Python steering committee and the broader developer community are currently reviewing a significant evolution in the language’s type hinting system through PEP 849, a proposal that seeks to remove long-standing restrictions on the expressions used within type annotations. Authored by Imogen Hergeth and sponsored by Jelle Zijlstra, PEP 849 addresses a fundamental limitation in how the Python interpreter handles type metadata at runtime. While Python currently allows any valid expression to be used as an annotation, the practical utility of these annotations is often constrained by the interpreter’s standard evaluation rules. PEP 849 proposes a shift toward an Abstract Syntax Tree (AST) based format for annotations, which would allow developers to use complex logic, ternary operators, and even bitwise operators in ways that were previously impossible without losing runtime introspectability.
The Evolution of Python’s Annotation Landscape
Since the introduction of type hints in PEP 484, Python has struggled to balance the needs of static analysis with the realities of runtime execution. Initially, annotations were evaluated as standard Python expressions at the time of definition. This led to "forward reference" issues, where a class could not refer to itself in a type hint because the class object did not yet exist. To solve this, PEP 563 introduced "Postponed Evaluation of Annotations," which turned all annotations into strings. However, this created a new set of problems for runtime libraries like Pydantic and FastAPI, which needed to evaluate those strings back into actual objects—a process that is often fragile and context-dependent.
PEP 849 represents the next stage of this journey, following the groundwork laid by PEP 649 (Deferred Evaluation of Annotations Using Descriptors) and the introduction of the annotationlib module. The core issue PEP 849 identifies is that many intuitive type expressions are currently "swallowed" or misinterpreted by the Python interpreter. For example, in languages like TypeScript, a union of literal values is written as 1 | 2 | 3. In current Python, writing x: 1 | 2 | 3 results in the interpreter performing a bitwise OR operation, evaluating the entire expression to the integer 3 before any type checker or runtime library can see the individual components. By moving to an AST-based reporting format, PEP 849 ensures that the original structure of the expression is preserved and accessible.
Chronology of the Proposal
The development of PEP 849 has followed a rigorous path through the Python Discourse community and official standards track.
- November 5, 2025: Imogen Hergeth introduced the initial concept in a Discourse thread titled "Simpler and more expressive type annotations." The discussion focused on the frustration of using
Literal[1, 2, 3]when simpler syntax felt more "Pythonic." - September 2, 2026: The first formal draft of the PEP was published, detailing the technical requirements for changing how the
__annotate__function operates. - September 20, 2026: PEP 849 was officially created and assigned its number, targeting Python version 3.16.
- September 22, 2026: A dedicated discussion thread was opened on the Python Discourse, where core developers began debating the performance trade-offs and the impact on the
typingmodule’s API.
The proposal is currently in the "Draft" status, undergoing active revision as the community evaluates the reference implementation hosted in a CPython fork.
Technical Mechanics: The AST Format
At the heart of PEP 849 is the introduction of a new format for the __annotate__ method. Currently, when a class or function is defined, Python generates an annotate function that can return annotations in various formats, such as VALUE (the evaluated objects) or STRING (the literal text). PEP 849 adds Format.AST (assigned the value 5 in the annotationlib.Format enum).
When a runtime consumer calls MyClass.__annotate__(Format.AST), the function will no longer return a simple dictionary of evaluated values. Instead, it returns a dictionary of AnnotationAST objects. Each object contains two critical pieces of data:
- The AST Node: An
ast.exprobject representing the parsed structure of the annotation. - The Namespace: A dictionary containing the local and global variables necessary to evaluate that AST in the correct context.
This dual-payload approach solves the "context loss" problem that plagued previous string-based solutions. Because the namespace is captured at the time of definition, the typing.get_type_annotations function—a new addition proposed by this PEP—can faithfully reconstruct the intended type objects even if they involve complex logic like ternary expressions (TypeA if Condition else TypeB) or comprehensions.
Performance Metrics and Impact Analysis
One of the most scrutinized aspects of any PEP is its impact on the Python interpreter’s performance. The authors of PEP 849 conducted extensive testing using a reference implementation to determine how these changes would affect different classes of users.
Import Time and Memory
For the vast majority of Python users—those who do not use type annotations or only use them for static analysis (linting)—the impact of PEP 849 is negligible. In fact, the reference implementation showed a moderate decrease in import times (a few percentage points) because the new annotate functions are more efficient at storing metadata than the current string-heavy or descriptor-heavy approaches. Memory footprint also saw a slight reduction of approximately 2-3% in annotated modules.
Runtime Evaluation
The primary performance cost is localized to users who actively evaluate type annotations at runtime, such as developers using dataclasses, Pydantic, or ORM frameworks like SQLAlchemy. The study found that in the "worst-case scenario"—where every annotation is requested in the VALUE format and all names are defined—the evaluation time could increase by up to sevenfold.
However, the authors argue that this is a "worthwhile trade-off." The current system already suffers from significant slowdowns when it encounters undefined names or requires FORWARDREF resolution. In those instances, the performance of the proposed AST format is comparable to the existing system. Furthermore, since type evaluation typically happens only once (e.g., when a class is first defined or a dataclass is initialized), the sevenfold increase in a millisecond-scale operation is unlikely to impact overall application performance for most web or data science workloads.
Ecosystem Reactions and Deprecations
The proposal has sparked a range of reactions from the Python ecosystem. Library maintainers who rely on runtime introspection have generally expressed support for the added expressiveness. The ability to use ternary operators in type hints, for example, would allow for more dynamic API definitions that currently require verbose overloads or complex TypeVar hacks.
A significant consequence of PEP 849 is the proposed deprecation of typing.get_type_hints. This function has been the standard way to retrieve annotations for years, but its internal logic is incompatible with the new AST-based resolution. The PEP suggests that users migrate to annotationlib.get_annotations or the newly proposed typing.get_type_annotations. While the authors admit this will cause friction, they argue that leaving get_type_hints as-is would lead to "silent failures" where new type syntax is incorrectly parsed by the old function, leading to subtle bugs in user code.
Broader Implications for Python’s Future
If adopted, PEP 849 would bring Python’s typing system closer to the flexibility found in TypeScript, which is often cited as the gold standard for developer experience in typed dynamic languages. By decoupling the syntax of an annotation from the semantics of the Python execution engine, the language allows the typing module to evolve independently of the core interpreter.
For instance, the proposed "Conditional Types" (discussed in the draft of PEP 827) would be trivial to implement under PEP 849. A developer could write:
def process(data: list[int] if SETTING_ENABLED else list[str]): ...
Under the current system, the interpreter would evaluate SETTING_ENABLED immediately and discard the "else" branch, making it invisible to any tool trying to understand the full scope of the function. Under PEP 849, the tool would receive the AST for the entire conditional expression, allowing it to provide smarter suggestions or stricter validation based on both possibilities.
Security and Implementation Status
From a security perspective, the PEP introduces no new vulnerabilities. The ability to execute code via annotations already exists in Python, and the AST format does not expose any information that isn’t already accessible through the inspect module or by reading the source code.
The reference implementation in the CPython fork serves as a proof of concept, demonstrating that the __annotate__ function can be modified to store binary AST data in string constants. This compact format ensures that the bytecode remains small while providing the necessary "hooks" for the typing module to perform its work. As Python 3.16 approaches its development cycle, the focus will shift to refining these helper functions and ensuring that the transition for library maintainers is as smooth as possible.
In summary, PEP 849 is a foundational proposal. It does not implement new types itself, but it builds the infrastructure necessary for the next decade of Python typing evolution. By allowing the language to "see" the structure of its own annotations without the interference of standard runtime evaluation, it opens the door for a more expressive, intuitive, and powerful type system.