# Python 3.15: Lazy Imports, frozendict, and a Faster JIT

> Source: <https://byteiota.com/python-3-15-lazy-imports-frozendict-jit/>
> Published: 2026-10-10 05:08:56+00:00

Python 3.15 shipped on October 9. The headline feature is [explicit lazy imports](https://peps.python.org/pep-0810/) — a syntax change that defers module loading until first use. Meta’s codebase saw a 70% reduction in initialization time after adopting the pattern. The release also ships `frozendict` as a built-in type, a proper sentinel, unpacking in comprehensions, and a JIT compiler that is finally showing numbers worth quoting. Here is what matters and what to watch.

## Lazy Imports: The Startup Fix Python Needed

For years, heavy Python apps had a startup problem. A web server importing Django, SQLAlchemy, and a handful of ML libraries could spend 400–800ms doing nothing but running import statements before accepting a single request. The standard fix was manual workaround: move imports inside functions, use `importlib` tricks, or fork CPython like Meta did. Python 3.15 makes this a first-class feature.

The new `lazy` soft keyword defers module loading until the imported name is first accessed:

``` python
lazy import json
lazy from pathlib import Path

# At this point, nothing is loaded — proxy objects only
# The real import fires on first use:
data = json.loads('{"key": "value"}')
```

For projects that need to stay compatible with older Python versions, there is a backwards-compatible approach using the `__lazy_modules__` list. On Python 3.15+, those imports become lazy. On older versions, the list is ignored and imports proceed normally.

``` python
__lazy_modules__ = ['numpy', 'pandas', 'heavy_analytics_lib']
import numpy
import pandas
```

The practical wins are real. CLI tools are the clearest example — when a user runs `--help`, there is no reason to import a plotting library or a database driver. Meta’s 70% initialization reduction was not a lab benchmark; it came from converting production libraries with deep dependency graphs.

Two risks worth naming. First: late error detection. An `ImportError` that used to surface at startup will now surface when you first access the lazy import, potentially during request handling or a background job. Second: thread safety. Startup was previously single-threaded; now any thread that first touches a lazy import triggers the load. Neither is a dealbreaker, but both require awareness when adopting this pattern in concurrent systems.

## frozendict: A Built-in That Took 20 Years

Python developers have needed an immutable, hashable dictionary for a long time. The standard library offered `types.MappingProxyType` as a partial answer, but it was a view over a mutable dict — not genuinely hashable, not usable as a dict key. Third-party packages filled the gap. Python 3.15 ships `frozendict` as a proper built-in via [PEP 814](https://peps.python.org/pep-0814/).

``` python
import functools

config = frozendict({"model": "gpt-4o", "temperature": 0.7})

# Use as a cache key
@functools.lru_cache
def run_query(cfg: frozendict) -> str:
    ...

# Use as a set member or dict key
seen_configs = {frozendict(user="alice"), frozendict(user="bob")}
lookup = {frozendict(scope="read"): handler_fn}
```

A few things to know before adopting it. `frozendict` is not a `dict` subclass — `isinstance(fd, dict)` returns `False`. That is intentional: it avoids inheriting mutation methods that would need to raise errors. The immutability is also shallow: a nested list inside a `frozendict` is still mutable, and a `frozendict` containing a list cannot be hashed. These are the same constraints that apply to `frozenset`, so the behavior is at least consistent.

## The JIT Keeps Compounding

Python’s experimental JIT has been technically available since 3.13. It has not been worth enabling until now. Python 3.15 shows 7–8% geometric mean improvement on x86-64 Linux and 11–12% on AArch64 macOS, with upgrades to LLVM 21, a new tracing frontend, basic register allocation, and lower memory usage for generated machine code.

The trajectory matters more than any single number: roughly 2% in 3.13, 4–5% in 3.14, 7–8% in 3.15. The JIT is still experimental and opt-in — it will not activate automatically. It is also not PyPy. But 8% on a distributed workload has real infrastructure cost implications, and the trend line is clearly heading somewhere.

## Smaller Wins Worth Knowing

Python 3.15 also ships `sentinel` as a built-in. The old `_MISSING = object()` pattern works fine but produces ugly reprs and does not pickle cleanly. `sentinel("MISSING")` gives you a named, picklable, identity-preserving sentinel that works properly with type annotations.

[PEP 798](https://peps.python.org/pep-0798/) adds unpacking to comprehensions:

```
# Before Python 3.15
flat = [x for sublist in lists for x in sublist]

# Python 3.15
flat = [*sublist for sublist in lists]

# Merge dicts in one comprehension
merged = {**d for d in dict_list}
```

## What Actually Breaks

UTF-8 is now the default encoding on all platforms. Code that calls `open()` without an explicit `encoding=` argument and relies on locale defaults — especially on Windows — may produce different output. The fix is to pass `encoding="utf-8"` explicitly.

Several long-deprecated APIs are now removed: `datetime.utcnow()` (use `datetime.now(datetime.UTC)`), `importlib.resources.read_text()`, and the internal `sre_compile`, `sre_constants`, `sre_parse` modules. The regex internals are the most likely surprise — some packages accessed them directly. Check your dependencies before upgrading. The [official What’s New page](https://docs.python.org/3.15/whatsnew/3.15.html) has the full list.

## Should You Upgrade?

For greenfield projects: yes. The new built-ins are cleaner than their alternatives, and lazy imports are worth adopting for anything with non-trivial startup cost. For existing projects: check your dependencies first. The UTF-8 default and removed APIs are the likeliest sources of breakage, and most projects will need small fixes rather than rewrites.

Python 3.15 is not a headline release — there is no walrus operator moment, no `match` statement. What it is: a release that fixes chronic annoyances that Python developers have been patching around for years. That is often more valuable than a flashy syntax addition. The [official download is available now](https://www.python.org/downloads/release/python-3150/).
