What's New in Python
A new version of Python is released every October, each supported for five years. If you're reading older tutorials or maintaining older code, it helps to know which features arrived when. This lesson summarises the most useful additions from Python 3.8 to 3.14.
Release timeline
| Version | Released | Headline features |
|---|---|---|
| 3.8 | 2019 | Walrus operator :=, f-string = debugging, positional-only parameters |
| 3.9 | 2020 | Dict merge |, list[int] hints, str.removeprefix, zoneinfo |
| 3.10 | 2021 | match statements, X | Y type unions, much better error messages |
| 3.11 | 2022 | 10–60% faster, exception groups and except*, tomllib, error locations in tracebacks |
| 3.12 | 2023 | Flexible f-strings, type statement and generic syntax, itertools.batched |
| 3.13 | 2024 | New interactive shell with colours and multi-line editing, experimental free-threaded build and JIT |
| 3.14 | 2025 | Template strings (t-strings), deferred annotations, officially supported free-threaded build |
Each version is maintained for about five years. Check python3 --version, and prefer a version that still receives updates.
Better error messages (3.10–3.13)
Recent versions point at the exact part of a line that failed and suggest fixes:
user = {"name": "Ana"}
print(usr["name"])
Traceback (most recent call last):
File "main.py", line 2, in <module>
print(usr["name"])
^^^
NameError: name 'usr' is not defined. Did you mean: 'user'?Pattern matching (3.10)
def handle(command): match command.split(): case ["load", filename]: return f"loading {filename}" case ["save", *_]: return "saving" case _: return "unknown" print(handle("load report.csv"), "|", handle("save as"), "|", handle("quit"))
loading report.csv | saving | unknown
Covered in full in lesson 15.
Modern type hint syntax (3.9–3.12)
type Grid = list[list[int]] def first[T](items: list[T]) -> T | None: return items[0] if items else None def total(rows: Grid) -> int: return sum(sum(r) for r in rows) print(first(["a", "b"]), total([[1, 2], [3, 4]]))
a 10
No more importing List, Optional and TypeVar from typing for everyday hints.
f-string improvements (3.8, 3.12)
data = {"name": "Ana", "scores": [90, 85]}
total = sum(data["scores"])
print(f"{total=}")
print(f"{data["name"]} averaged {total / len(data["scores"]):.1f}")
print(f"{'\n'.join(['multi', 'line'])}")
total=175 Ana averaged 87.5 multi line
Since 3.12, f-strings can reuse the same quotes inside braces, contain backslashes, and span lines.
Exception groups and notes (3.11)
try: raise ExceptionGroup("validation failed", [ValueError("bad email"), TypeError("age not a number")]) except* ValueError as eg: print("value errors:", [str(e) for e in eg.exceptions]) except* TypeError as eg: print("type errors:", [str(e) for e in eg.exceptions])
value errors: ['bad email'] type errors: ['age not a number']
Reading TOML config (3.11)
import tomllib config = tomllib.loads(""" [server] host = "localhost" port = 8080 [features] beta = true """) print(config["server"]["port"], config["features"]["beta"])
8080 True
TOML is the format of pyproject.toml; tomllib reads it without third-party packages.
Template strings (3.14)
A t prefix creates a template instead of a finished string. Code receiving it can see the literal parts and the values separately — for example, to escape values safely before building HTML or SQL:
from html import escape from string.templatelib import Interpolation def safe_html(template): parts = [] for item in template: if isinstance(item, Interpolation): parts.append(escape(str(item.value))) else: parts.append(item) return "".join(parts) user_input = "<script>alert('hi')</script>" print(safe_html(t"<p>Hello, {user_input}!</p>"))
<p>Hello, <script>alert('hi')</script>!</p>
An f-string would have inserted the script tag directly; the t-string lets the function escape each value before combining.
Performance and free threading
Python 3.11 brought large speed-ups (often 25% or more) through the "Faster CPython" project, and work continues each release, including an experimental just-in-time (JIT) compiler. Python 3.13 introduced an optional free-threaded build that removes the Global Interpreter Lock (GIL), allowing threads to run Python code truly in parallel on multiple CPU cores; 3.14 made it officially supported. The standard build still has the GIL, and libraries are gradually adding support.
Upgrading
- Read the "What's New" page in the official documentation for each version you skip.
- Run your tests on the new version before switching (tools like
toxornoxtest several versions at once). - Linters such as Ruff (with the
pyupgraderules) can automatically rewrite old syntax to modern equivalents.