Every October like clockwork, a new Python lands — and this year’s is a good one. Python 3.15.0 was released on October 9, 2026, the product of 5,643 commits from 1,012 contributors, and it brings the kind of practical, working-developer improvements that make upgrading feel worthwhile rather than obligatory.
One important note first: Python 3.15.1 dropped yesterday (October 21) as an expedited security release. It ships around 330 bugfixes plus an upgraded bundled pip (26.2.2 with certifi 2026.10.19) to address the eMudhra/emSign CA certificate incident. If you’re installing fresh, install 3.15.1; if you’re already on 3.15.0, upgrade — and python.org explicitly advises upgrading pip and uv in your existing virtual environments too.
Here’s what actually matters in the release.
Lazy imports: the headline feature
PEP 810 adds a new lazy soft keyword. Mark an import as lazy and Python defers loading the module until the name is first used — no more moving imports into functions or restructuring code to shave seconds off startup:
lazy import json
lazy from pathlib import Path
print("Starting up...") # json and pathlib aren't loaded yet
data = json.loads('{"key": "value"}') # json loads here
A few details worth knowing: lazy imports are only allowed at module scope (using lazy inside a function or a try block raises SyntaxError), star imports can’t be lazy, and import failures surface at first use rather than at import time — with a traceback that includes both the access point and the original import statement. You can also flip everything lazy globally with -X lazy_imports, the PYTHON_LAZY_IMPORTS environment variable, or sys.set_lazy_imports(), with a filter API for making only your own packages lazy. And for libraries that still support older Pythons, a module-level __lazy_modules__ list makes plain import statements lazy on 3.15+ while staying harmless elsewhere.
The biggest win is for CLI tools and applications with deep dependency trees that only touch a fraction of their imports on any given run.
Two new builtins: frozendict and sentinel
PEP 814 adds frozendict to builtins — an immutable dictionary that preserves insertion order, is hashable when its keys and values are, and is not a subclass of dict:
>>> a = frozendict(x=1, y=2) >>> a['z'] = 3 TypeError: 'frozendict' object does not support item assignment
The standard library has been updated to accept it (copy, json, pickle, pprint, decimal, and more), and eval()/exec() accept it for globals. Handy for config defaults, cache keys, and anywhere you’ve been defensively copying dicts.
PEP 661 adds the sentinel builtin, which finally standardizes the _MISSING = object() idiom:
MISSING = sentinel('MISSING')
def read_value(default=MISSING):
...
Sentinels get a clean, readable repr, preserve identity through copy and pickling (when importable by module and name), and can be used directly in type expressions like int | MISSING. Note they’re truthy, unlike None — check with is, as always.
UTF-8 is now the default encoding
PEP 686 makes UTF-8 the default for text I/O regardless of the OS locale. A bare open() now behaves the same on Windows, macOS, and Linux, which ends a whole class of cross-platform encoding bugs. The change bites hardest on Windows, where the locale default was rarely UTF-8. If you have code that depends on the old behavior, PYTHONUTF8=0 opts out.
Tachyon: profile a running process without restarting it
PEP 799 adds a dedicated profiling package. profiling.tracing is the relocated cProfile (which remains as an alias), and profiling.sampling is the new hotness: Tachyon, a statistical sampling profiler that hits up to 1,000,000 Hz with near-zero overhead. You can attach it to an already-running process by PID — no code changes, no restart — and choose what to measure: wall-clock time (the default), CPU time, or GIL-holding time. A dump mode prints a one-shot traceback-style stack of every thread (or all asyncio tasks with --async-aware), which is genuinely useful for investigating hung production processes. The old pure-Python profile module is deprecated and will be removed in 3.17.
Faster, and friendlier to observability
The experimental JIT compiler got a significant upgrade: 7–8% geometric-mean improvement on x86-64 Linux over the standard interpreter, and 11–12% on AArch64 macOS over the tail-calling interpreter, on the official benchmark suite — your mileage will vary, and it’s still experimental. Separately, the official Windows 64-bit installers now ship the tail-calling interpreter (Real Python reports 15–20% over the old switch-based build), and the macOS installers now include free-threading support by default, backed by a stable ABI for free-threaded builds (PEPs 803, 820, 793). PEP 831 enables frame pointers by default, so system-level profilers get sane native stack traces.
Also in this release: unpacking in comprehensions (PEP 798), TypedDict with typed extra items (PEP 728), TypeForm annotations (PEP 747), disjoint bases in the type system (PEP 800), package startup configuration files (PEP 829), and more color in command-line output.
Trying it out
The easiest path is uv: uv run --python 3.15 python downloads a standalone interpreter that sits alongside — not instead of — your current Python. Or grab the official installers from python.org. Real Python’s “What’s New” tutorial is a solid guided tour, and the official What’s New page covers every PEP in depth.
This feels like a release aimed squarely at working developers: faster startup for the tools you ship, fewer encoding surprises across platforms, and a profiler that meets your production process where it lives. Lazy imports alone are worth the upgrade for anyone maintaining a CLI.