Programming LanguagesPerformancePython

Python 3.15: Lazy Imports, frozendict, and What to Fix Now

Python 3.15 features: lazy imports, frozendict builtin, Tachyon sampling profiler, JIT improvements

Python 3.15 landed on October 9. It ships the lazy keyword, a built-in frozendict, a zero-overhead sampling profiler, and a faster JIT. Most of your Python 3.14 code will run unchanged — but if you open files on Windows without specifying an encoding, you already have a bug. Here is what changed, what to fix, and what to start using.

The UTF-8 Default is the One Breaking Change Worth Acting On

PEP 686 makes UTF-8 the default encoding for all text I/O in Python 3.15. The change is almost invisible on Linux and macOS, which already default to UTF-8 in most locales. Windows is a different story. The previous default there was the ANSI code page — typically cp1252. Any open() call without an explicit encoding= argument now silently uses UTF-8 instead.

That means files written in one Python version can be misread in another. Depending on the data, you get a UnicodeDecodeError, garbled text, or — worse — silent corruption that only surfaces later in your pipeline. Find every affected call now:

python3.15 -X warn_default_encoding -m pytest

Fix is one line per affected call:

# Broken on Windows in 3.15 (implicit encoding)
with open("config.toml") as f:
    data = f.read()

# Correct everywhere
with open("config.toml", encoding="utf-8") as f:
    data = f.read()

If you have a large codebase and need time to audit, PYTHONUTF8=0 restores the old behavior temporarily. Treat it as a bridge, not a solution. PEP 597’s EncodingWarning will surface every remaining gap.

Lazy Imports: The Feature CLI Authors Have Hacked Around for Years

The lazy soft keyword lets you defer a module import until the name is first accessed. This is not new conceptually — importlib.util has had a lazy loader since 3.7, and half the tools ecosystem has been wrapping it for years. What’s new is that it’s now language syntax, not an 8-line workaround.

lazy import pandas as pd
lazy from pathlib import Path

def process_data(filepath):
    return pd.read_csv(filepath)  # pandas loads here, not at startup

The practical payoff is significant for CLI tools and scripts with optional heavy dependencies. If a user runs your tool without hitting the pandas code path, pandas never loads. Startup times can improve by 20x in heavy-import scenarios.

The limits are worth knowing upfront: lazy only works at module scope. You cannot use it inside functions. Star imports and future imports cannot be lazy. And if two modules have a circular dependency, lazy imports will not save you — they can make diagnosis harder if names are accessed during module initialization.

frozendict Is Now a Builtin

The PyPI frozendict package pulled 3.8 million downloads per week before this release. That demand signal was impossible for the Steering Council to ignore. PEP 814 accepted, and the type is now in builtins.

frozendict is an immutable mapping. It is not a subclass of dict. It is hashable when all keys and values are hashable (the freeze is shallow). That hashability is the real unlock:

import functools

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

# Valid as a dict key
lookup = {config: "primary"}

# Works with lru_cache
@functools.lru_cache()
def build_pool(cfg):
    return ConnectionPool(cfg["host"], cfg["port"])

build_pool(config)  # hashable, so caching works

types.MappingProxyType existed before this, but it was not hashable and not a true type — just a read-only view. frozendict is what you actually wanted. The upgrade cost here is zero: your old code is not affected, and you only adopt it where it makes sense.

Tachyon: Profile Production, Not Just Dev

Python 3.15 ships a sampling profiler called Tachyon under profiling.sampling. The key difference from cProfile (which moves to profiling.tracing with a backward-compatible alias) is overhead. cProfile instruments every function call and return — in practice, it adds 10–50% runtime overhead. Tachyon samples the call stack from outside the process at 1,000 times per second by default, with negligible impact on the running program.

More practically: you can attach it to a running production process without a restart.

# Profile a script from scratch
python -m profiling.sampling run app.py

# Attach to a running process (no restart required)
python -m profiling.sampling attach 12345

That attach command is the part that matters. You have seen a slow request in your logs. Instead of adding instrumentation, deploying, and waiting to reproduce the issue, you attach and get a flamegraph now.

The JIT: Worth Testing, Not Yet Default

The experimental JIT compiler in 3.15 is meaningfully improved over 3.14. The pyperformance suite shows an 8–9% geometric mean speedup on x86-64 Linux and 12–13% on AArch64 macOS. Long-running pure-Python scripts on Windows see up to 40%. The gains come from expanded bytecode coverage, register allocation, and better constant propagation.

It is still opt-in. Set PYTHON_JIT=1 and run your test suite. If nothing breaks, leave it on in staging and measure. The roadmap targets default-on around 3.17.

Should You Upgrade Now?

If you are on 3.14, the answer is yes — on a timeline. Run the encoding warning pass first (python3.15 -X warn_default_encoding -m pytest), fix what surfaces, then move. If you are on 3.10 or earlier, note that 3.10 reached end-of-life with this release. You have more debt to address, but 3.15’s compatibility is high enough that the jump is reasonable once your encoding and removed-API checklist is clear.

Python 3.15 is not a dramatic reshaping of the language — it is a compound of well-understood requests that sat in PEP limbo for years. Lazy imports, frozendict, and Tachyon are each individually useful. Combined, they close off a category of “Python kinda does not have that” complaints that accumulated since 3.10. The UTF-8 default is the only thing that will catch you off-guard if you skip the audit.

The full changelog is on the official What’s New in Python 3.15 page. Real Python has a detailed feature walkthrough with runnable examples for each new addition.

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 *