Free Python reference · JavaScript timeline

Python 3.12: Cleaner generics and fewer f-string restrictions

Type parameters and type aliases gain dedicated syntax; f-strings accept more ordinary Python expressions.

Released · Editorial review October 25, 2026

Introduced in Python 3.12 · Typing

Type parameters and type aliases

Declare a generic parameter beside the function name with def first[T](...). That parameter connects the input element type to the result type for static checking. The type statement gives an alias a distinct declaration and can introduce its own parameters.

Earlier code commonly declared a separate TypeVar and assigned an alias to a name. The newer syntax keeps the parameter close to the definition that uses it. It still does not enforce element types during ordinary execution.

The example annotates a small score table and returns its first score. Its input is nonempty; if you need an empty-input behavior, design that contract rather than expecting the type parameter to supply one.

Earlier approach

from typing import TypeVar
T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

Context fragment; read it with the example below.

Worked example

Minimum Python 3.12

type ScoreTable = dict[str, list[int]]

def first[T](items: list[T]) -> T:
    return items[0]

scores: ScoreTable = {"Ada": [8, 9]}
print(first(scores["Ada"]))

Expected output

8

Example checked with CPython 3.14.8, CPython 3.15.0, Pyodide Python 3.13.2.

Common mistake

The type statement creates a type alias, not a class for validating records. Generic relationships need a supporting type checker to be checked statically.

Try it yourself

Call first with a list of strings. What return type should a type checker infer, and what happens for an empty list?

Read more: Python 3.12 release notes · PEP 695

Introduced in Python 3.12 · Syntax

F-strings accept more expression syntax

Python 3.12 integrates f-strings into the regular grammar and removes several earlier restrictions. You can reuse the outer quote style inside an expression, include backslashes in string literals inside expressions, and use multiline expressions with comments.

These changes help when formatting values selected from dictionaries or joining several lines. Compatibility still matters: a source file using the newer form will fail to parse on an older interpreter before any version-checking branch can run.

The example uses double quotes both for the f-string and the dictionary key. When supporting Python 3.11, alternate quote styles or compute the intermediate value first.

Worked example

Minimum Python 3.12

record = {"name": "Ada"}
print(f"Learner: {record["name"]}")
parts = ["one", "two"]
print(f"Joined: {" / ".join(parts)}")

Expected output

Learner: Ada
Joined: one / two

Example checked with CPython 3.14.8, CPython 3.15.0, Pyodide Python 3.13.2.

Common mistake

More permissive grammar does not make a dense expression readable. Introduce a named intermediate value when the formatting becomes hard to scan.

Try it yourself

Rewrite the first example so it also parses on Python 3.11, without changing its output.

Read more: Python 3.12 release notes · PEP 701

Recap

Type parameters and type aliases gain dedicated syntax; f-strings accept more ordinary Python expressions. Prefer the clearest form that works on your project's supported Python versions.

Read the full Python 3.12 release notes →