NewsDeveloper ToolsProgramming LanguagesPython

Python 3.15: Lazy Imports, frozendict, Tachyon Now Stable

Python 3.15 release featured image showing lazy imports frozendict and Tachyon profiler

Python 3.15.0 shipped on October 9 — two days ago — and it is the most developer-ergonomics-focused release in years. Four things you have been hacking around manually for a long time are now built in: lazy imports, an immutable dict type, a standard sentinel, and a production-safe profiler. One of those features has a deployment trap that will cost you real production incidents if you ship without reading the fine print.

Lazy Imports: The Feature and the Trap

PEP 810 lands the lazy keyword. The syntax is straightforward:

lazy import json
lazy from pathlib import Path

print("Starting up...")        # json and pathlib not loaded yet
data = json.loads('{"key": "value"}')   # json loads here

For CLIs with dozens of heavy imports, serverless functions fighting cold start times, or any app that imports pandas and torch at the top level — this is a genuine win. Python has made you pay the full import cost at startup regardless of which code path your process actually runs. That changes with 3.15.

Now the trap. Lazy imports defer ImportError from startup to first access. A deploy goes green. Your readiness probe passes. The process joins the load balancer. The first real request hits the code path that uses the missing module, and you get a 500. Not a crash. Not a restart. The process stays up looking healthy while returning errors on every request that triggers that import. This failure mode is well documented — it converts a deploy-time crash into a production error rate.

Worse: a failed lazy import does not poison the lazy object. It retries on every access. You get the same ImportError on every request until you notice and restart.

The fix is a preflight check before your server accepts traffic:

import importlib

# Force eager load of critical dependencies before accepting requests
for mod in ["stripe", "celery", "redis"]:
    importlib.import_module(mod)

There is also a silent failure mode for registry patterns. If a module runs a @register decorator at import time, and you lazy-import it but nothing ever touches the name, the registration never happens — no error, no traceback, just a plugin that is not there. Audit your codebase for import-time side effects before going lazy.

frozendict: Immutable, Hashable, and Actually Usable

PEP 814 adds frozendict as a builtin. The immediately useful part is immutable config and fixed lookup tables — but the hashable part is where it earns its keep:

config = frozendict(env="prod", debug=False, retries=3)

# Use as a dict key or set member
seen = {config: "result"}

# Works directly with lru_cache
from functools import lru_cache

@lru_cache
def get_connection(settings: frozendict):
    return db.connect(**settings)

types.MappingProxyType has covered this territory since Python 3.3 but was never hashable. frozendict is hashable when all its keys and values are hashable. One gotcha before upgrading: isinstance(x, dict) returns False for frozendict. Switch to isinstance(x, (dict, frozendict)) or — better — isinstance(x, collections.abc.Mapping).

Tachyon: Profile Production Python Without Slowing It Down

PEP 799 ships the profiling package with two modules: profiling.tracing (deterministic, replaces the deprecated profile module) and profiling.sampling (statistical — the new stuff).

Tachyon samples at up to 1,000,000 Hz with near-zero overhead. cProfile slows your code 2–20x because it hooks every function call. Tachyon attaches to a running process without a restart:

python -m profiling.sampling run script.py
python -m profiling.sampling attach --live 12345   # live view, no restart
python -m profiling.sampling --flamegraph run script.py

Wall clock, CPU, and GIL-holding modes give you different views of where time is going. GIL mode is particularly useful for threading bottlenecks — a question that gets more relevant as free-threading stabilizes. The profile module is now soft-deprecated; migrate to profiling.tracing when you get around to it (removal is 3.17).

sentinel: Retire the _MISSING = object() Pattern

Every Python codebase of any size has this somewhere:

_MISSING = object()

def get(key, default=_MISSING):
    if default is _MISSING:
        raise KeyError(key)

PEP 661 replaces it with a proper builtin:

MISSING = sentinel("MISSING")

def get(key, default=MISSING):
    if default is MISSING:
        raise KeyError(key)

The difference: sentinel("MISSING") gives you a clean repr, pickle support, and type-union compatibility. When you combine this with frozendict and lazy import in the same file, 3.15 starts to feel like a proper cleanup release.

The Quiet Wins: UTF-8, JIT, and Comprehension Unpacking

Three more changes worth knowing before you upgrade.

UTF-8 default encoding (PEP 686): open("file.txt") now uses UTF-8 regardless of your OS locale. This eliminates the “works on my Mac, breaks on Alpine Linux in CI” encoding bug — but it silently breaks any code relying on locale encoding. Set PYTHONUTF8=0 or pass encoding="locale" explicitly if you need the old behavior.

JIT improvement: The experimental JIT is 7–8% faster on x86-64 Linux and 11–12% on AArch64 macOS. Still opt-in (python --enable-experimental-jit), still experimental — but this is the first release where the improvement is consistently visible on real workloads.

Comprehension unpacking (PEP 798): You can now unpack sequences inside comprehensions:

[*L for L in nested_lists]      # flatten nested lists
{**d for d in list_of_dicts}    # merge dicts (later keys win)

Upgrade Checklist

Before you pin 3.15 in production, check these:

  • Any open() calls without encoding= now use UTF-8 — test against non-UTF-8 data.
  • Code using isinstance(x, dict) will miss frozendict instances.
  • load_module() is removed from Loader — migrate to exec_module().
  • NamedTuple keyword-argument constructor syntax is gone — use class or functional form.
  • __cached__ attribute on modules is removed — use __spec__.cached.
  • If using lazy imports in a server: add a preflight check to force-load critical deps before accepting traffic.

Python 3.15 is a release that charges you in test time what it pays back in production quality. The full changelog is worth a read, but these six items cover the bulk of what will affect your day-to-day code. The lazy import gotcha deserves a second read before you ship — the rest of it is just the standard library catching up to what everyone was doing by hand.

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