Python 3.15 is out, and the release notes are worth reading beyond the changelog. Lazy imports land as an explicit language feature β not a third-party hack β and in real applications they reduce startup time by up to 70%. A built-in frozendict type eliminates years of PyPI workarounds. A production-grade sampling profiler ships in the standard library. The JIT actually moves the needle on compute-heavy code. This is the release where CPython stops apologizing for startup cost and import bloat.
Lazy Imports: The Feature Python Developers Have Been Waiting For #
Python 3.15 introduces the lazy soft keyword via PEP 810. Write lazy import numpy and NumPy does not load until the first line that actually uses it. The module loads on first reference β a process called reification β not at import time.
lazy import numpy as np # loads only when np is first referenced
lazy import tensorflow as tf # same
def train(data):
return tf.keras.Sequential(...) # tf loads here, not at startup
The numbers are striking. When Google demonstrated PEP 810 on the google-cloud-compute library, startup imports dropped from 1,407 modules to 15. PEP 810 itself reports 50β70% startup time reductions in large applications, with 30β40% memory savings. CLI tools, Django projects, and test suites are the immediate winners.
Two things to understand before adopting it everywhere. First, the design is intentionally granular: only imports explicitly marked lazy are deferred. Libraries do not get lazy behavior unless their authors opt in. Second, import errors surface at first use, not at startup. If a lazy-imported module does not exist, you find out the moment your code tries to use it β not when the application starts. That is a behavior change worth testing before shipping to production.
The backward compatibility story is clean: on Python 3.14 and earlier, the lazy declaration is silently ignored and the import runs eagerly. You can adopt the syntax today without breaking older environments.
frozendict: A Built-In Immutable Dict, Finally #
The frozendict PyPI package has been downloaded tens of millions of times. Python developers have wanted an immutable, hashable dict since at least 2012. PEP 416 tried and was rejected. PEP 814 succeeded, and frozendict ships as a built-in in 3.15.
config = frozendict({"host": "localhost", "port": 5432})
cache = {config: result}
seen = {config}
@functools.lru_cache
def connect(params: frozendict) -> Connection:
...
A frozendict is not a subclass of dict β it inherits directly from object. That means isinstance(fd, dict) returns False. Existing code that type-checks with isinstance(x, dict) will not automatically accept a frozendict. Worth auditing before you refactor your codebase to use it everywhere.
Beyond caching, the practical use cases are config objects that should not mutate after construction, thread-safe read-only mappings (no locks needed), and function default arguments that do not share state across calls. If you have been using types.MappingProxyType as a workaround, note that proxy is not hashable β frozendict is.
Tachyon: A Production Profiler Ships in stdlib #
Python profiling has been awkward for years. cProfile is built in but adds 20β40% runtime overhead. Production profiling required third-party tools like py-spy or Austin that you had to install, configure, and justify to your security team.
Python 3.15 ships profiling.sampling β codenamed Tachyon β a statistical sampling profiler with near-zero overhead that can attach to already-running processes.
python -m profiling.sampling attach 12345
python -m profiling.sampling run --flamegraph slow_script.py
python -m profiling.sampling run --live server.py
Tachyon samples at up to 1,000,000 Hz and outputs interactive HTML flame graphs natively, without extra tooling. PEP 799 also reorganizes the existing profiling landscape: profiling.tracing is cProfile under a new, logical home. For teams that have been shipping py-spy alongside their applications to enable production profiling, this is now a stdlib capability.
JIT: Real Gains, Honest Limitations #
Python 3.15 JIT compiler uses LLVM 21 for stencil generation and adds basic register allocation, reducing stack operations on hot paths. The pyperformance benchmark suite shows a 7β8% geometric mean improvement on x86-64 and 11β12% on AArch64. The Mandelbrot benchmark specifically dropped from 5.25 seconds to 2.47 seconds β roughly 2x on that workload.
Ten of twelve pyperformance benchmarks are faster. Two are marginally slower. The JIT benefits code that spends time in pure Python computation β scientific computing, numerical preprocessing, algorithmic Python. If your code is I/O-bound, dominated by calls into C extensions, or mostly handled by an async event loop, the JIT gains will not show up. That is not a knock on the work; it is just what JIT compilers do.
Before You Upgrade: What Actually Breaks #
Python 3.15 biggest migration hazard is not a feature β it is a default behavior change. The release finalizes UTF-8 as the default encoding for open(), read_text(), write_text(), and stdio. Any code that implicitly relied on Windows code pages or platform-native encodings will break silently or raise errors.
Run this before upgrading:
python -X warn_default_encoding -m pytest
Fix every open() and read_text() call that lacks an explicit encoding= argument. This matters most on Windows and when processing files written by legacy systems.
- distutils is gone β migrate to
setuptools,flit, orhatchif you have not already - asyncio.get_event_loop() deprecation tightens β switch to
asyncio.run()or explicit loop creation - importlib.metadata now raises
MetadataNotFoundinstead of returning empty metadata - argparse enables
suggest_on_error=Trueby default
To install Python 3.15 today, the fastest path is uv:
uv python install 3.15
uv venv --python 3.15
Lazy imports are the headline, but the broader picture here is that Python is systematically addressing its long-standing criticisms: startup cost, immutable data types, and production observability. The ecosystem is overdue to treat these as first-class concerns. Python 3.15 makes that a lot easier. Full details in the official What is New in Python 3.15.