# Python 3.15 Ships: Lazy Imports, frozendict, Faster JIT

> Source: <https://byteiota.com/python-3-15-ships-lazy-imports-frozendict-faster-jit/>
> Published: 2026-11-11 17:07:51+00:00

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](https://peps.python.org/pep-0810/). 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.

``` python
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](https://peps.python.org/pep-0416/) tried and was rejected. PEP 814 succeeded, and `frozendict` ships as a built-in in 3.15.

```
config = frozendict({"host": "localhost", "port": 5432})

# Use as a dict key
cache = {config: result}

# Use in a set
seen = {config}

# Use as an lru_cache argument
@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](https://github.com/benfred/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.

```
# Profile a running process without restarting it
python -m profiling.sampling attach 12345

# Generate a flame graph from a script
python -m profiling.sampling run --flamegraph slow_script.py

# Live top-style view
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](https://pyperformance.readthedocs.io/) 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` , or`hatch` if you have not already
- **asyncio.get_event_loop()** deprecation tightens — switch to`asyncio.run()` or explicit loop creation
- **importlib.metadata** now raises`MetadataNotFound` instead of returning empty metadata
- **argparse** enables`suggest_on_error=True` by default

To install Python 3.15 today, the fastest path is [uv](https://docs.astral.sh/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](https://docs.python.org/3.15/whatsnew/3.15.html).
