Python 3.15.0 is scheduled for 1 October 2026. The second release candidate shipped on 1 September, which means the feature set is frozen and the only thing left to change is bug fixes. If you run Python in production, the window for finding out what breaks is now, not in October.
This is a larger release than the version number suggests. Two changes — free-threading reaching non-experimental status and explicit lazy imports — have been years in the making. A third, UTF-8 becoming the default encoding, is the one most likely to break something quietly.
Free-threading is no longer experimental, but it is still opt-in
The headline is easy to misread. Free-threading is no longer labelled experimental in 3.15 — but the interpreter you get when you type python3.15 still has the GIL. The free-threaded build is a separate binary, `python3.15t`, and you have to choose it deliberately.
What actually changed for most people is packaging. PEP 803 defines a Stable ABI for free-threaded builds — abi3t — so a C extension can now be compiled once against the stable ABI and work on free-threaded CPython. Until now, supporting free-threading meant maintainers shipped a separate wheel matrix for a platform almost nobody was running, which is exactly the chicken-and-egg problem that kept adoption near zero.
Getting there is not free: extensions have to migrate to the new PyModExport_* export hooks from PEP 793. So the honest summary is that 3.15 removes the structural blocker to a free-threaded ecosystem, not that the ecosystem is ready. If your workload is numeric, GIL-bound and leans on NumPy/SciPy-class extensions, start testing under python3.15t — and expect to find libraries that are not ready. If your workload is a web service that already scales by process, this release changes nothing for you yet.
Lazy imports (PEP 810)
PEP 810 adds a lazy soft keyword:
lazy import json
lazy from pathlib import Path
print("Starting up...") # neither module has been loaded
data = json.loads('{"key": 1}') # json is imported here, on first useThere is also a process-wide switch — -X lazy_imports or the PYTHON_LAZY_IMPORTS environment variable — for turning it on without editing source.
Be clear about who this helps. Import time is paid once per process, so lazy imports matter for things that start processes constantly: CLI tools, serverless functions, short-lived workers, test runners spawning subprocesses. A Django or FastAPI service that starts once and runs for a week gains essentially nothing at steady state, though it may shave cold-start time on a scale-from-zero platform.
The trap is that deferred imports move failures. An ImportError that used to happen at startup now happens on the first call, possibly in production, possibly in a request handler. If you turn on the process-wide flag, make sure something in CI actually exercises the paths that trigger those imports.
UTF-8 is now the default encoding
Under PEP 686, Python now uses UTF-8 as the default encoding for I/O rather than the locale-dependent encoding. You can turn it off with PYTHONUTF8=0 or -X utf8=0.
For anyone who has ever debugged a UnicodeDecodeError that only happened on one Windows machine, this is the right change. It is also the change most likely to alter behaviour silently in code that was, without realising it, relying on cp1252 or a locale-specific codec — file reads without an explicit encoding=, subprocess output parsing, CSV handling on Windows. Those calls will not error; they will produce different bytes.
The mitigation is boring and effective: grep for open( without an encoding= argument, and fix them. That is worth doing whether or not you upgrade.
A real profiler in the standard library
PEP 799 reorganises Python's profiling tools under a profiling package and adds Tachyon, a statistical sampling profiler that supports sampling rates up to 1,000,000 Hz, attaches to an already-running process, and can profile by wall clock, CPU, GIL or exception time. It emits pstats, flame graphs, Gecko format, heatmaps and an interactive TUI.
Being able to attach to a live process without restarting it or pre-instrumenting it is the part that matters operationally. Related: PEP 831 builds CPython with -fno-omit-frame-pointer by default, which makes native stack unwinding work properly for external profilers and debuggers too.
The old profile module is deprecated in favour of profiling.tracing.
Smaller things you will actually use
- `frozendict` (PEP 814) — an immutable, hashable builtin mapping. It does not inherit from
dict; it inherits fromobject. Cleaner thantypes.MappingProxyTypefor read-only config and for anything you want to use as a dict key. - `sentinel` (PEP 661) — a proper builtin for unique sentinel values, with a sane
repr, pickling support, and usable in|type expressions. Retires a decade of_MISSING = object(). - Unpacking in comprehensions (PEP 798) —
[*L for L in lists]and{**d for d in dicts}now work. - Better error messages —
AttributeErrornow suggests attributes reachable through intermediate objects, and recognises method names borrowed from other languages ([1,2,3].push(4)suggestsappend). - Typing —
TypeForm(PEP 747),TypedDictgainingclosedandextra_items(PEP 728), and disjoint bases (PEP 800). - Colour by default in the interactive shell, help output, error messages and several CLI tools, controlled by
NO_COLOR/FORCE_COLOR.
Removals to check before you upgrade
3.15 removes a long list of previously deprecated APIs across ast, collections.abc, ctypes, datetime, glob, http.server, importlib, pathlib, platform, threading, types, typing, wave and zipimport. The __cached__ module attribute is no longer set or consulted. The What's New document also lists re.match() as soft-deprecated in favour of an explicit re.prefixmatch() — nothing breaks today, but it signals where that API is going.
None of these are exotic modules. Run your test suite against the release candidate before October rather than finding out from a deployment.
What this changes
For most teams, nothing on 1 October. Python releases are adopted slowly and correctly so — 3.15 will not be your base image for months.
What you should do in the next two weeks is cheap:
1. Run CI against 3.15.0rc2 as an allowed-failure job. The removals list is long enough that something will surface.
2. If you maintain a package on PyPI, build and publish 3.15 wheels now. Wheels built against the release candidates will work with the final release, and the whole ecosystem's upgrade speed depends on maintainers doing this before the release rather than after it.
3. Audit open() calls without an explicit encoding, ahead of the UTF-8 default.
4. If — and only if — you have a genuinely GIL-bound CPU workload, try python3.15t and record which of your dependencies fail. That list is more useful than a benchmark.
Free-threaded Python is not the 2026 story. It is the story of whatever release lands after the extension ecosystem finishes migrating to abi3t, and 3.15 is what makes that migration possible.