Python 3.15 Release Candidate 2 landed September 1, and the ABI is now frozen for the entire 3.15 series. For most developers, that’s background noise. For package maintainers who ship binary wheels, it is an action item with a hard deadline: October 1. Wheels built against RC2 are production-compatible with all future 3.15.x releases. If your package is missing from PyPI on launch day, your users get a broken install. Thirty days. No more waiting.
The ABI Freeze: What It Actually Means
When the Python team freezes the ABI, they’re committing that no internal data structure layouts will change for the rest of the 3.15 series. That matters for C extension authors and anyone shipping compiled wheels. The practical result: wheels you build today against 3.15.0rc2 will work on 3.15.0, 3.15.1, and every patch release after that. You don’t need to wait for the final release to start publishing.
The Python Software Foundation is unusually direct here, calling on maintainers to “prepare for 3.15 and publish wheels on PyPI.” Read that as a polite but firm request. Packages without 3.15 wheels will fail pip installs on Python 3.15. That’s users filing bug reports against your project for something you had 30 days to fix.
Lazy Imports: The Feature Everyone Has Been Waiting For
PEP 810 ships what the Python community has been manually implementing for years. The problem: about 17% of stdlib imports are already stuffed inside functions specifically to defer loading. It’s a workaround, not a feature. Now there’s actual syntax.
lazy import json
lazy from pathlib import Path
print("Starting up…") # json and Path are NOT loaded yet
data = json.loads('{"key": "val"}') # loads here, on first access
The implementation uses lightweight proxy objects rather than loading modules at import time. When you first access the name, the module loads and replaces itself — zero overhead after that. Organizations report 50–70% startup time reductions for CLI tools and 30–40% memory savings in large applications. You can also enable it globally with -X lazy_imports=all without touching code.
One constraint worth noting: lazy imports only work at module scope. You can’t use them inside functions or classes, and star imports don’t qualify. The tradeoff is that import-time side effects — module-level registration patterns, for instance — are deferred too. Worth auditing before you switch on the global flag.
frozendict and sentinel: Python Fills Two Annoying Holes
These aren’t flashy, but they solve real problems that developers have hacked around for years.
frozendict (PEP 814) is an immutable, hashable dictionary. Unlike a plain dict, it can be used as a dictionary key, added to a set, and passed as an argument to LRU-cached functions. The mutable default argument trap — the classic mistake of writing def f(x={}): — becomes less of a hazard when you have a genuinely immutable mapping as your default.
config = frozendict(host='localhost', port=5432)
hash(config) # works — frozendict is hashable
{config: True} # works as a dictionary key
sentinel (PEP 661) is the built-in version of _MISSING = object(), the pattern everyone writes but nobody can pickle. The new built-in is identity-preserving when copied, pickleable when the name is importable, and supports type expression syntax.
NOT_FOUND = sentinel('NOT_FOUND')
Neither of these is a reason to rush to 3.15 on its own. Together, they chip away at the “Python makes simple things annoying” complaints that push developers toward other languages.
Performance: Numbers Worth Knowing
The JIT compiler in 3.15 delivers an 8–9% geometric mean speedup on x86-64 Linux. On AArch64 macOS — Apple Silicon Macs — the gain is 12–13%. Windows 64-bit builds now use the tail-calling interpreter by default. These are not hand-wavy “up to” numbers from marketing copy; they’re from the official What’s New documentation.
The new profiling package (PEP 799) replaces the scattered cProfile tooling with a proper statistical profiler called Tachyon. It samples at up to one million Hz, can attach to running processes, and outputs interactive HTML flamegraphs, Firefox Profiler format, and live terminal dashboards. GIL contention analysis is one flag away:
python -m profiling.sampling --attach <pid> --mode gil
python -m profiling.sampling --run script.py --mode wall --flamegraph
Breaking Changes That Will Catch You Off Guard
UTF-8 is now the default encoding for open(), compile(), and ast.parse() regardless of system locale. On most Linux and macOS systems, this is already the case. On Windows, it isn’t — code that relied on cp1252 or another locale encoding will silently change behavior. The fix is mechanical: add explicit encoding= arguments everywhere. The detection command is python -W error yourscript.py.
A few long-deferred removals also land in 3.15. __cached__ on modules is gone — use __spec__.cached. locale.getdefaultlocale() is finally removed (it was supposed to go in 3.13). Import statements in .pth files no longer work; use .start files. Run your test suite under -W error before you assume your code is clean.
The Migration Checklist
If you’re upgrading from Python 3.10, note that 3.10 reaches end of life October 31, 2026 — the same month 3.15 ships. Two countdowns running at once. Average Python migration takes 4–8 weeks factoring in dependency testing and CI updates. If you haven’t started, start now.
- Replace
__cached__with__spec__.cached - Add explicit
encoding=to all file I/O calls - Migrate
.pthimport statements to.startfiles - Replace
locale.getdefaultlocale()withlocale.getencoding() - Run
python -W errorto surface remaining deprecation warnings - If you maintain binary wheels: build against RC2 and publish to PyPI now
Python 3.15 is a strong release. Lazy imports alone are worth the upgrade for any project with a startup time problem. But the ABI freeze means the clock is already running, and October 1 does not move.













