Python 3.14 Preview: Lazy Annotations

Python 3.14 Preview: Lazy Annotations

Recent Python releases have introduced several small improvements to the type hinting system, but Python 3.14 brings a single major change: lazy annotations. This change delays annotation evaluation until explicitly requested, improving performance and resolving issues with forward references. Library maintainers might need to adapt, but for regular Python users, this change promises a simpler and faster development experience.

By the end of this tutorial, you’ll understand that:

  • Although annotations are used primarily for type hinting in Python, they support both static type checking and runtime metadata processing.
  • Lazy annotations in Python 3.14 defer evaluation until needed, enhancing performance and reducing startup time.
  • Lazy annotations address issues with forward references, allowing types to be defined later.
  • You can access annotations via the .__annotations__ attribute or use annotationlib.get_annotations() and typing.get_type_hints() for more robust introspection.
  • typing.Annotated enables combining type hints with metadata, facilitating both static type checking and runtime processing.

Explore how lazy annotations in Python 3.14 streamline your development process, offering both performance benefits and enhanced code clarity. If you’re just looking for a brief overview of the key changes in 3.14, then expand the collapsible section below:

Python 3.14 introduces lazy evaluation of annotations, solving long-standing pain points with type hints. Here’s what you need to know:

  • Annotations are no longer evaluated at definition time. Instead, their processing is deferred until you explicitly access them.
  • Forward references work out of the box without needing string literals or from __future__ import annotations.
  • Circular imports are no longer an issue for type hints because annotations don’t trigger immediate name resolution.
  • Startup performance improves, especially for modules with expensive annotation expressions.
  • Standard tools, such as typing.get_type_hints() and inspect.get_annotations(), still work but now benefit from the new evaluation strategy.
  • inspect.get_annotations() becomes deprecated in favor of the enhanced annotationlib.get_annotations().
  • You can now request annotations at runtime in alternative formats, including strings, values, and proxy objects that safely handle forward references.

These changes make type hinting faster, safer, and easier to use, mostly without breaking backward compatibility.

Take the Quiz: Test your knowledge with our interactive “Python Annotations” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Python Annotations

Test your knowledge of annotations and type hints, including how different Python versions evaluate them at runtime.

Python Annotations in a Nutshell

Before diving into what’s changed in Python 3.14 regarding annotations, it’s a good idea to review some of the terminology surrounding annotations. In the next sections, you’ll learn the difference between annotations and type hints, and review some of their most common use cases. If you’re already familiar with these concepts, then skip straight to lazy evaluation of annotations for details on how the new annotation processing works.

Annotations vs Type Hints

Arguably, type hints are the most common use case for annotations in Python today. However, annotations are a more general-purpose feature with broader applications. They’re a form of syntactic metadata that you can optionally attach to your Python functions and variables.

Although annotations can convey arbitrary information, they must follow the language’s syntax rules. In other words, you won’t be able to define an annotation representing a piece of syntactically incorrect Python code.

To be even more precise, annotations must be valid Python expressions, such as string literals, arithmetic operations, or even function calls. On the other hand, annotations can’t be simple or compound statements that aren’t expressions, like assignments or conditionals, because those might have unintended side effects.

Python supports two flavors of annotations, as specified in PEP 3107 and PEP 526:

  1. Function annotations: Metadata attached to signatures of callable objects, including functions and methods—but not lambda functions, which don’t support the annotation syntax.
  2. Variable annotations: Metadata attached to local, nonlocal, and global variables, as well as class and instance attributes.

The syntax for function and variable annotations looks almost identical, except that functions support additional notation for specifying their return value. Below is the official syntax for both types of annotations in Python. Note that <annotation> is a placeholder, and you don’t need the angle brackets when replacing this placeholder with the actual annotation:

Python Syntax Python 3.6+
class Class:
    # These two could be either class or instance attributes:
    attribute1: <annotation>
    attribute2: <annotation> = value

    def method(
        self,
        parameter1,
        parameter2: <annotation>,
        parameter3: <annotation> = default_value,
        parameter4=default_value,
    ) -> <annotation>:
        self.instance_attribute1: <annotation>
        self.instance_attribute2: <annotation> = value
        ...

def function(
    parameter1,
    parameter2: <annotation>,
    parameter3: <annotation> = default_value,
    parameter4=default_value,
) -> <annotation>:
    ...

variable1: <annotation>
variable2: <annotation> = value

To annotate a variable, attribute, or function parameter, put a colon (:) just after its name, followed by the annotation itself. Conversely, to annotate a function’s return value, place the right arrow (->) symbol after the closing parenthesis of the parameter list. The return annotation goes between that arrow and the colon denoting the start of the function’s body.

As shown, you can mix and match function and method parameters, including optional parameters, with or without annotations. You can also annotate a variable without assigning it a value, effectively making a declaration of an identifier that might be defined later.

Declaring a variable doesn’t allocate memory for its storage or even register it in the current namespace. Still, it can be useful for communicating the expected type to other people reading your code or a static type checker. Another common use case is instructing the Python interpreter to generate boilerplate code on your behalf, such as when working with data classes. You’ll explore these scenarios in the next section.

To give you a better idea of what Python annotations might look like in practice, below are concrete examples of syntactically correct variable annotations:

Python Python 3.6+
>>> temperature: float
>>> pressure: {"unit": "kPa", "min": 220, "max": 270}

You annotate the variable temperature with float to indicate its expected type. For the variable pressure, you use a Python dictionary to specify the air pressure unit along with its minimum and maximum values. This kind of metadata could be used to validate the actual value at runtime, generate documentation based on the source code, or even automatically build a command-line interface for a Python script.

Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Article

Already a member? Sign-In

Locked learning resources

The full article is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Article

Already a member? Sign-In

About Bartosz Zaczyński

Bartosz is an experienced software engineer and Python educator with an M.Sc. in Applied Computer Science.

» More about Bartosz

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

What Do You Think?

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Become a Member to join the conversation.

Keep Learning

Related Topics: intermediate python