pyguides

What's New in Python 3.14: Deferred Annotations & More

Python 3.14, released in October 2025, is what’s new and worth upgrading for: it delivers long-awaited changes to how annotations work, introduces a new string prefix for safe templating, and continues the ongoing effort to make the interpreter faster. Here is a look at the most important features and what they mean for your code.

Deferred evaluation of annotations

PEP 649 changes how Python evaluates type annotations. In previous versions, annotations in function signatures and variable declarations were evaluated eagerly at definition time. This caused practical problems: forward references required quoting the type as a string, and importing types solely for annotations created circular import headaches.

In Python 3.14, annotations are evaluated lazily. They are stored internally as a compact form and only materialized into actual objects when you explicitly access them through typing.get_type_hints() or the __annotations__ descriptor.

Before Python 3.14, a forward reference required a string:

class Tree:
    def add_child(self, child: "Tree") -> None:
        self.children.append(child)

This works because Python now stores annotations in a deferred form rather than evaluating them immediately. When the function definition runs, the interpreter records the annotation expression but does not resolve the name until you explicitly request the type hints. This deferred resolution sidesteps the definition-order problem that previously forced developers to quote forward references. With PEP 649, the class name no longer needs to exist at definition time:

class Tree:
    def add_child(self, child: Tree) -> None:
        self.children.append(child)

The approach taken here eliminates the need for from __future__ import annotations (PEP 563), which converted all annotations to strings. PEP 649’s approach preserves the actual semantics of the annotation expressions while still deferring their evaluation.

For most codebases, this change is transparent — existing correctly-typed code continues to work. The main benefit is that you can remove defensive string quoting and __future__ imports.

Template Strings (t-strings)

PEP 750 introduces template strings, a new string prefix t"..." that produces a Template object instead of a plain string. Template strings look like f-strings but give you programmatic access to the interpolated values before they are rendered, enabling safe construction of SQL queries, HTML, and other structured text.

from string.templatelib import Template

name = "Alice"
greeting = t"Hello, {name}!"
# greeting is a Template object, not a string

A Template object contains the static parts of the string and the interpolated values as separate components. You can iterate over these components, identify which parts are user-supplied values versus fixed text, and apply transformations only to the dynamic portions. This separation is what makes template strings safe for contexts where naive string interpolation would lead to injection vulnerabilities. The following function walks through each component of a template and escapes any interpolated values for safe HTML output:

from string.templatelib import Template

def make_safe_html(template: Template) -> str:
    parts = []
    for item in template:
        if isinstance(item, str):
            parts.append(item)
        else:
            # item is an Interpolation; escape its value
            from html import escape
            parts.append(escape(str(item.value)))
    return "".join(parts)

username = "<script>alert('xss')</script>"
html = make_safe_html(t"<p>Welcome, {username}</p>")
# Result: <p>Welcome, &lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;</p>

The key advantage over f-strings is that the template is not immediately flattened into a string. Libraries can define their own rendering logic, applying escaping, parameterized queries, or validation as needed. This makes t-strings especially useful for web frameworks, ORMs, and any library that constructs text from untrusted input. You can also subclass Template to customize how interpolation values are formatted, giving library authors fine-grained control over the string building process.

Improved error messages

Python 3.14 continues the trend started in Python 3.10 of producing clearer, more actionable error messages. Several improvements make debugging easier:

  • More precise messages for common TypeError and ValueError scenarios, including the specific types or values involved.
  • Better suggestions when a name is misspelled, leveraging improved edit-distance matching.
  • Clearer tracebacks when exceptions occur inside comprehensions and generators.

For example, calling a function with the wrong number of arguments now produces a message that explicitly states what was expected versus what was provided, with improved formatting for keyword arguments. This kind of precision saves developers from counting parameters manually when a mismatch occurs:

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}"

greet("Alice", "Bob", "Charlie")
# TypeError: greet() takes from 1 to 2 positional arguments but 3 were given

While the improvement here is incremental, the cumulative effect across many error categories reduces the time spent diagnosing issues. These better messages are particularly useful for newcomers who may not yet recognize common Python error patterns, making the language more approachable without changing any runtime behavior.

Performance Improvements

Python 3.14 includes several interpreter-level optimizations building on the work from 3.11 through 3.13:

  • Faster function calls through continued specialization of the adaptive interpreter, reducing overhead for common calling patterns.
  • Reduced memory usage for objects with __slots__ and for small dictionaries.
  • Improved startup time, benefiting short-lived scripts and CLI tools.

The exact speedup varies by workload, but benchmarks on the pyperformance suite show measurable gains across a range of real-world programs. These improvements are automatic and require no code changes. The adaptive interpreter, first introduced in Python 3.11, continues to benefit from more specialized bytecode instructions that handle common patterns without the overhead of generic dispatch. For most applications, the speedup is noticeable without any effort on the developer’s part.

What’s new when upgrading

Python 3.14 can be installed alongside existing Python versions. To install it:

# On Ubuntu/Debian (via deadsnakes PPA)
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.14

# Using pyenv
pyenv install 3.14.0
pyenv local 3.14.0

# Verifying the installation
python3.14 --version

Before upgrading a project, keep these considerations in mind:

  • Annotation behavior change: If your code inspects __annotations__ directly at class or module level, test that it still works as expected with deferred evaluation. Using typing.get_type_hints() is the recommended approach going forward.
  • Third-party library support: Check that your dependencies have published wheels compatible with 3.14. Most major libraries had support ready by the release date, but niche packages may lag behind.
  • Deprecation removals: As with every release, some previously deprecated APIs have been removed. Run your test suite with deprecation warnings enabled (python -W error::DeprecationWarning) on 3.13 first to catch issues early.

What’s new in Python 3.14 makes it a worthwhile upgrade for the annotation improvements alone, and the performance gains and template strings add further incentive to make the move. The combination of cleaner type annotations, safer string handling, and a faster runtime makes this one of the most practical Python releases in recent years. For the complete list of every change, the official Python 3.14 release notes cover every PEP, deprecation, and C API change in full.

See also