Python 3.15 Beta 2 shipped June 2 with its feature set locked. The final release lands in October, but three features are worth understanding now: lazy imports cut cold-start time with a single keyword, frozendict retires a decade of third-party workarounds, and the Tachyon profiler attaches to live production processes without a restart or code change. Here is what changed, how to use it, and how to try it today.
Lazy Imports (PEP 810): One Keyword, Faster Startup
Large Python applications—CLIs, Django management commands, AI inference scripts—suffer from slow cold starts because Python executes every top-level import at launch, even when most of those modules are never touched for that specific invocation. PEP 810 fixes this with a new lazy soft keyword.
lazy import json
lazy from pathlib import Path
The module loads the first time the name is actually accessed, not at import time. For heavy dependency trees, this can shave hundreds of milliseconds off cold startup with zero refactoring of existing logic.
For projects that need backward compatibility with Python 3.14 and earlier, PEP 810 includes a declarative escape hatch:
__lazy_modules__ = {"numpy", "pandas", "scipy"}
import numpy
import pandas
import scipy
On Python 3.15 and above, those imports become lazy automatically. On older versions, the attribute is ignored and imports proceed eagerly as normal. One declaration, zero compatibility shims.
There are hard restrictions: lazy is permitted only at module scope. Using it inside a function, class body, or try/except block raises a SyntaxError. Star imports and __future__ imports cannot be lazy. One important caveat for teams running strict type checking: both mypy and astral’s ty have open issues for PEP 810 support and do not handle the lazy keyword yet. Keep that in mind before rolling it out in strictly-typed codebases.
frozendict Built-in (PEP 814): Python Fixes a 14-Year Oversight
PEP 416, an earlier attempt to add an immutable dict built-in, was rejected in 2012. PEP 814 is the version that made it through. As of 3.15, frozendict ships in builtins—no import required.
config = frozendict(host="localhost", port=5432, debug=False)
# Hashable — usable as a dict key or set element
seen = {config}
# Works directly with lru_cache
import functools
@functools.lru_cache(maxsize=None)
def connect(cfg: frozendict) -> Connection:
...
The design choices matter. frozendict is not a dict subclass—it inherits directly from object—which keeps the immutability contract clean. It preserves insertion order, offers O(1) lookup, and is hashable as long as all keys and values are hashable. This is what types.MappingProxyType was never able to offer: MappingProxyType is not hashable, and it still holds a reference to the underlying mutable dict.
The third-party frozendict package on PyPI has roughly 12 million weekly downloads—a reliable signal that Python developers have been working around this gap for years. The built-in version retires that dependency.
Tachyon Profiler: py-spy Moves into the Standard Library
The stdlib has had cProfile for deterministic profiling, but deterministic profilers add overhead on every function call, which makes them unsuitable for production use. The standard answer has been to reach for py-spy, a Rust-based external sampling profiler. Python 3.15 brings equivalent functionality into the stdlib as profiling.sampling.
# Profile a script and generate an HTML flamegraph
python -m profiling.sampling run --format flamegraph -o profile.html app.py
# Attach to a LIVE running process — no restart, no code changes
python -m profiling.sampling attach --pid 12345
# Dump an async-aware snapshot
python -m profiling.sampling dump --pid 12345 --async-aware
Tachyon samples at up to 1,000,000 Hz with near-zero overhead by running externally to the target process. It outputs pstats, collapsed stacks, HTML flamegraphs, and heatmaps. The async-aware mode understands coroutines, which matters for the majority of modern Python services running on asyncio or anyio. The key capability is the live attach: profile a process that is already running in production without touching its code or restarting it.
JIT and Free-Threading: The Numbers Behind the Upgrade
Two more wins ship quietly with the 3.15 upgrade. The JIT compiler now posts an 8–9% geometric mean speedup over the standard CPython interpreter on x86-64 Linux, and 12–13% on AArch64 macOS. Those gains arrive for free—no configuration required.
For C extension authors, PEP 803 introduces a stable ABI for free-threaded builds (abi3t). Python 3.14 made running without the GIL possible. Python 3.15 makes it practical for the extension ecosystem: a single compiled binary can now target multiple minor versions of free-threaded CPython. This is the missing piece that allows NumPy, SciPy, and the scientific stack to start building no-GIL wheels without shipping a separate binary per minor version.
How to Try It Now
If you are using uv—and at this point you should be—installing a beta Python takes a single command:
uv python install 3.15
uv run --python 3.15 your_script.py
October is closer than it looks. The Python team wants bug reports during the beta window. If you maintain a library, test your builds against 3.15b2 now and file issues before the release candidate locks the ABI. If you are an application developer, run your test suite under 3.15, enable a lazy import or two, and measure the startup delta. The tooling gap for type checkers is real but temporary—mypy and ty both have open tracking issues.
Python 3.15 is not a revolution. It is a competent release that closes gaps that should have been closed years ago and raises the performance baseline without demanding anything from existing code. That is often how the best Python releases work.
The full feature list is in the official What’s New in Python 3.15 documentation. Beta 2 release notes are on the Python Insider blog.













