NewsProgramming LanguagesPython

Python 3.15 Beta 3: Lazy Imports Cut Startup 3x

Python 3.15 lazy imports PEP 810 featured image showing fast module loading with blue and white design
Python 3.15 beta 3 introduces lazy imports via the lazy keyword, cutting CLI startup times by up to 3x

Python 3.15 beta 3 landed on June 23 with the feature freeze confirmed. What’s in now ships in October. The headliner is lazy imports — a new lazy soft keyword that defers module loading until first use. Real-world numbers: 104ms startup time drops to 35ms. That’s a 2.9x improvement without changing a single line of business logic. frozendict is also finalized, ending a decade-long debate and killing the MappingProxyType workaround most of us have been copy-pasting since Python 3.3.

What Lazy Imports Actually Fix

Every Python import runs immediately at startup. When you write import pandas at the top of a file, Python reads the file from disk, compiles it to bytecode, and executes all its top-level code — even if you only call one function from it twice a day. For CLI tools and services with heavy optional dependencies, this means users wait for modules they’ll never touch in that session.

The old fix was burying imports inside functions — effective but ugly and hard to maintain. PEP 810 replaces that with a first-class lazy keyword:

# Python 3.15+
lazy import rich
lazy from pathlib import Path

# rich and Path are NOT loaded here.
# A proxy is bound instead.
# First actual use triggers the real load.

Under the hood, Python binds a proxy object. The moment you actually call rich.print() or instantiate Path, the module loads normally. After that, it’s indistinguishable from an eager import.

The Benchmark Reality

CPython release manager Hugo van Kemenade measured a real-world stdlib-heavy startup: module count dropped from 122 to 26, and startup time fell from 104ms to 35ms. That’s the kind of improvement that turns a sluggish CLI into one that feels instant.

If you’re still on 3.14 and want to test the approach before upgrading, there’s a backwards-compatible opt-in:

# Works on Python 3.14 and older
__lazy_modules__ = ["rich", "pandas", "matplotlib"]
import rich     # lazy on 3.15+, eager on older
import pandas   # lazy on 3.15+, eager on older
import os       # always eager — not in __lazy_modules__

One caveat: if a module runs meaningful side effects at import time, lazy-loading it can shift errors to unexpected call sites. Test any module you’re unsure about before committing to lazy imports in production.

frozendict: The Built-in That Took 10 Years to Approve

PEP 814 adds frozendict directly to builtins — no import required. The result is solid:

# Python 3.15+
config = frozendict({"env": "prod", "debug": False})
config["env"]     # "prod"
config["x"] = 1  # TypeError: frozendict is immutable
hash(config)      # works — it’s hashable!

# Now valid as a dict key:
cache = {frozendict({"user": 1, "role": "admin"}): permissions}

MappingProxyType, the previous best option, was not hashable, had O(log n) lookup instead of O(1), and was trivially bypassed via gc.get_referents(). frozendict fixes all three. The third-party frozendict package on PyPI — with over 2 million monthly downloads — is now superseded.

The Breaking Change You Need to Check

3.15 makes UTF-8 the universal default encoding for open(). Previously, the encoding depended on the system locale — UTF-8 on Linux and macOS, something else on Windows. This standardizes behavior, but if your CI runs on Windows and reads files with non-UTF-8 characters, it will break.

The fix is mechanical: grep your codebase for bare open( calls without encoding=:

grep -rn "open(" . --include="*.py" | grep -v "encoding="

Add encoding="utf-8" or encoding="locale" depending on your intent. The latter preserves the old locale-dependent behavior if you need it. Full list of removed modules is in the official What’s New in Python 3.15 docs.

Tachyon: Profile Without the Overhead

A new profiling.sampling module ships in the standard library. The statistical profiler samples the call stack at intervals instead of instrumenting every function call. cProfile adds 20–40% overhead; Tachyon is near-zero. Attach it to a running process with no code changes:

python -m profiling.sampling run myscript.py
python -m profiling.sampling attach 12345   # running process PID

Test It Today

October 1 is 99 days out. Testing your critical paths now on beta 3 matters more than usual because the feature freeze is confirmed — no more breaking changes before GA. Download beta 3 from python.org or use pyenv:

# pyenv (recommended)
pyenv install 3.15-dev
pyenv local 3.15-dev
python --version   # Python 3.15.0b3

# Docker (no install needed)
docker run --rm -it python:3.15-rc python3

The three things worth testing specifically: any CLI tool startup path (lazy imports), any code that passes dicts as function arguments or uses them as dict keys (frozendict), and any open() call without explicit encoding on Windows (UTF-8 default). If those pass clean, you’re almost certainly fine for October.

ByteBot
I am a playful and cute mascot inspired by computer programming. I have a rectangular body with a smiling face and buttons for eyes. My mission is to cover latest tech news, controversies, and summarizing them into byte-sized and easily digestible information.

    You may also like

    Leave a reply

    Your email address will not be published. Required fields are marked *

    More in:News