
Python 3.15 RC1 landed on August 4. The final release is October 1 — six weeks away. That’s your window to test your applications, check your dependencies, and get ahead of three features that will change everyday Python development: explicit lazy imports via PEP 810, a built-in frozendict type, and the Tachyon sampling profiler. The JIT also got a meaningful upgrade — but we’ve covered that separately. This post is about the three features your code will feel first.
Good news for library authors: RC1 is ABI-stable. Any wheel you build against RC1 works on the final 3.15.0 and all future 3.15.x releases. There is no reason to wait.
Lazy Imports (PEP 810): The Feature Python Has Needed Since Forever
Python’s import system has always been eager: write import numpy, and Python immediately finds the file, reads it from disk, compiles bytecode, and executes every line of top-level code. For a script that imports 30 heavy libraries but uses 5 of them in a given run, you’re paying the full cost every time.
PEP 810 fixes this with a new lazy keyword that defers module loading until first use:
lazy import numpy as np
lazy from pathlib import Path
# numpy is NOT loaded yet
# It loads the moment you access: np.array(...)
Don’t want to annotate every import? Use the global interpreter flag:
python -X lazy_imports myscript.py
# or
PYTHON_LAZY_IMPORTS=1 python myscript.py
For fine-grained control in library code, sys.set_lazy_imports_filter() lets you target specific module names:
import sys
sys.set_lazy_imports_filter(lambda name: name.startswith("heavy_"))
The results are real. Meta’s internal codebase reported startup time reductions of up to 70% using lazy imports on large Python applications where most imports are never touched in a given execution path. CLI tools, Django management commands, FastAPI startup — anything that boots before doing work stands to benefit.
One restriction worth knowing: lazy only works at module level. You can’t use it inside functions, classes, or try blocks. Star imports stay eager. That’s the right call — implicit lazy behavior inside function bodies would be genuinely confusing, and this design keeps the feature visible and opt-in.
Built-in frozendict: Finally
The Python community has been asking for an immutable, hashable dict since at least 2012 — PEP 416 was rejected then. PEP 814 finally made it in. frozendict is now a built-in:
config = frozendict(host="localhost", port=5432, debug=False)
config["host"] # "localhost"
config["port"] = 80 # TypeError: 'frozendict' object does not support item assignment
# Hashable — use it as a dict key or set member
cache = {}
cache[frozendict(x=1, y=2)] = "computed_result"
visited = {frozendict(x=1), frozendict(x=2)}
# Equality ignores insertion order
assert frozendict(x=1, y=2) == frozendict(y=2, x=1) # True
Important: frozendict is not a dict subclass — it inherits directly from object. If your code does isinstance(obj, dict) checks, this matters. It’s hashable as long as all keys and values are hashable, exactly like frozenset relative to set.
Compare with types.MappingProxyType, which wraps a mutable dict and isn’t hashable. frozendict is genuinely immutable. The standard library — copy, json, pickle, pprint — all accept it.
The Tachyon Profiler: cProfile’s Retirement Begins
cProfile has a fundamental problem: it instruments every function call. That’s what makes it deterministic — and what makes it slow. When you profile with cProfile, you’re measuring a different program than the one your users run. Python 3.15 adds profiling.sampling — the Tachyon sampler — which takes a statistical approach instead. It periodically captures stack traces at up to 1,000,000 Hz with virtually zero overhead. It supports threads, async functions, free-threading builds, and attaching to a running process without restarting it:
python -m profiling.sampling run myscript.py
python -m profiling.sampling attach 12345 # attach to running PID
Or programmatically:
from profiling.sampling import Sampler
with Sampler(mode="cpu", rate=10_000) as s:
expensive_function()
s.dump("profile.pstats")
The old cProfile implementation moves to profiling.tracing; cProfile itself stays as an alias. The profile module is deprecated in 3.15 and removed in Python 3.17. If your code or CI scripts import profile, migrate now. Tools like py-spy have filled this gap for years — it’s long past time Python owned this story.
What to Do Before October 1
RC1 is stable enough to act on. Here’s the short list:
- Library authors: publish 3.15 wheels to PyPI now. ABI is locked. Check the wheel readiness tracker — 90% of the top 360 packages are already there.
- Application developers: run your test suite against RC1.
pyenv install 3.15.0rc1and see what breaks while you have time to fix it. - CI pipelines: add a
3.15matrix entry. Catch issues before GA. - Deprecation watch: search your codebase for
import profileand migrate tocProfileorprofiling.tracing. Python 3.17 will remove it. - Lazy imports: find your slow-starting CLI tools or services, test with
-X lazy_imports. Some codebases will see real gains with zero code changes.
The official RC1 announcement links directly to the full What’s New guide. RC2 is September 1, GA is October 1 — six weeks to get ahead of this.













