
Python 3.15 RC1 landed on August 4. The ABI is now frozen — meaning any wheel you build against this release runs on the final 3.15 and every patch that follows. For library maintainers, this is the “no more excuses” moment. For everyone else, it is the clearest preview yet of what October brings: lazy imports that cut startup overhead by up to 70%, a hashable frozendict built into the language itself, a faster JIT, and a profiler that samples at one million hertz. If you are still treating 3.15 as a future problem, it is time to recalibrate.
Lazy Imports Actually Ship
PEP 810 has been circulating since 2025. It finally lands as a stable, opt-in feature. The idea: add a lazy keyword before an import statement and Python defers loading that module until the first time you actually use the name. No loading, no execution, nothing — until you need it.
lazy import json
lazy from pathlib import Path
print("Starting...") # Neither module has loaded yet
data = json.loads('{"key": "value"}') # json loads right here
The numbers make this worth caring about. Meta internal benchmarks showed up to 70% reduction in startup time across large codebases. A real-world CLI that imported pandas and numpy at the top of every file but only used them for certain subcommands dropped from 850ms to 120ms when those imports went lazy. AWS Lambda cold starts shed 150-400ms.
The design choice that got PEP 810 through (where PEP 690 did not) was explicit opt-in. Lazy loading only happens where you write lazy. No silent behavior changes, no ecosystem surprises. You can also enable it process-wide without touching source code via -X lazy_imports or the PYTHON_LAZY_IMPORTS environment variable.
A few restrictions worth knowing: lazy works at module scope only. It will not work inside functions, class bodies, try blocks, or with star imports. If you have been wrapping imports in functions for conditional loading, lazy imports replace that pattern at the module level — but conditional logic inside functions still needs to import the usual way.
frozendict: A Decade of Debate, Now a Built-in
The frozendict discussion has been running in Python circles for over ten years. Third-party packages filled the gap. Now PEP 814 ships it as a genuine built-in — no import needed, full ecosystem support out of the box.
The surface-level description is “immutable dict.” The more interesting property is that frozendict is hashable when all its keys and values are hashable. That unlocks patterns that were not idiomatically possible before.
config = frozendict(host="localhost", port=5432, db="prod")
# Use a dict as a cache key — finally works
cache = {config: connection_pool}
# Works with lru_cache
import functools
@functools.lru_cache()
def connect(params: frozendict):
return create_connection(**params)
# Comparison ignores insertion order
a = frozendict(x=1, y=2)
b = frozendict(y=2, x=1)
print(a == b) # True
print(hash(a) == hash(b)) # True
The json, pickle, copy, and pprint modules all understand frozendict natively. You can also pass a frozendict to eval() and exec() as the globals argument — a long-requested capability for sandboxed environments. This is a cleaner solution than every configuration library that has been rolling its own frozen-dict equivalent for years.
JIT Gets Real Speedups, Tachyon Profiler Arrives
The JIT compiler in 3.15 posts 8-9% geometric-mean speedups on x86-64 Linux and 12-13% on AArch64 macOS. Windows 64-bit switches to a tail-calling interpreter. These numbers compound alongside lazy imports reducing startup overhead.
More useful for daily work: the new Tachyon profiler in profiling.sampling. The old cProfile moves to profiling.tracing with a backward-compatible alias. Tachyon samples at up to 1,000,000 Hz with near-zero overhead on running processes. The --async-aware flag handles async/await code correctly — something the old profiling story notoriously failed at. Run it with --live for a top-like interactive view. Note: the profile module is deprecated in 3.15 and is removed in 3.17.
Only 9.4% of Top PyPI Packages Are Ready
As of today, only 34 of the top 360 PyPI packages declare Python 3.15 support — 9.4%. Ready packages include numpy, requests, pytest, black, mypy, and sqlalchemy. Not yet ready: pandas, boto3, cryptography, pydantic, scipy, Flask, and click.
RC1 is the moment to act. The official release post is clear: “binary wheels built against RC versions will work with future 3.15 releases.” ABI stable means build now, push to PyPI, and you are done before the October 1 final release creates a scramble. Three steps for maintainers: run your test suite against RC1, add Programming Language :: Python :: 3.15 to your classifiers, and push updated wheels.
What Breaks: Migration Checklist
- UTF-8 default encoding:
open()now uses UTF-8 regardless of system locale. Opt out withPYTHONUTF8=0or-X utf8=0. - API removals: Deprecated items from ast, ctypes, datetime, pathlib, and the typing module are gone.
- re.match() soft deprecated: Use
re.prefixmatch()instead — it is clearer about what it actually matches. - profile module deprecated: Migrate to
profiling.tracing(cProfile alias works, but plan ahead for 3.17). - Audit command: Run
python -Wd -W erroragainst your test suite to surface breakage early.
RC2 ships September 1. The final Python 3.15.0 drops October 1. The what’s new documentation has the full changelog. For lazy import semantics, PEP 810 is worth reading — it explains why this design got accepted where earlier proposals did not.













