Products#Code generation

Python 3.15 ships with lazy imports

PEP 810 lazy imports and UTF-8 by default land, the JIT gains 7-8%, and free-threading gets a stable ABI.

A ball python gliding through green grass

Python 3.15.0 shipped as stable on October 9 (supported through October 2031), and its theme is the promotion of long-running experiments: PEP 810 explicit lazy imports, PEP 686 UTF-8 as the default encoding, and a stable ABI for free-threaded builds. Release artifacts have moved fully to Sigstore signatures (OpenPGP signing was dropped as of 3.14).

Key points

  • PEP 810 lazy imports: a new lazy import keyword defers module loading until first use, with PYTHON_LAZY_IMPORTS and sys.set_lazy_imports() as controls — CLI startup times benefit directly
  • PEP 686 UTF-8 by default: open() now defaults to UTF-8 regardless of system locale; PYTHONUTF8=0 opts out
  • JIT: the experimental compiler is 7-8% faster on x86-64 Linux (11-12% on AArch64 macOS per pyperformance), with a new tracing frontend; still off by default
  • Free-threading: PEP 803 establishes the abi3t stable ABI; macOS official installers now include free-threading support by default
  • New builtins: frozendict (PEP 814) and sentinel (PEP 661); PEP 798 allows unpacking inside comprehensions
  • Security: multiple tarfile CVE fixes; Sigstore replaces OpenPGP

Three long arguments, closed

Version 3.15 quietly ends three of Python’s oldest fights. UTF-8 by default kills the encoding trap that made a generation of Windows users memorize encoding='utf-8'. The abi3t stable ABI means C-extension authors can finally ship stable binaries against the no-GIL build — free-threading’s exit from the GIL has moved from roadmap to engineering schedule. And lazy imports concede how Python’s role has changed: when import torch costs seconds and gigabytes, deferred loading is a cost decision, not a style preference.

Lazy imports in practice

The lazy import keyword pays off immediately in three settings: CLI tools (startup back to milliseconds), Lambda-style functions (where cold starts are billed by the millisecond), and multi-agent processes that import the same heavy stack over and over. Migration is deliberately cheap — imports without lazy behave exactly as before, so teams can mark only their heaviest dependencies and leave everything else untouched. Because it is a language keyword rather than an import-hook trick, behavior is predictable under every tooling setup — no magic to debug. PYTHON_LAZY_IMPORTS=1 doubles as a global trial switch: enable it in a test environment, watch what breaks, then annotate deliberately.

The compatibility minefield

Real upgrade risk concentrates in two places. Locale-dependent legacy code breaks quietly now that default encoding changed, and C extensions need rebuilds for free-threading compatibility — abi3t is a new standard and old wheels do not magically qualify. The data-science stack deserves special caution: until pandas, numpy and friends ship abi3t wheels, free-threaded builds can only run pure-Python workloads usefully. The What’s New ordering is the right one: confirm the UTF-8 default passes your test suite first, then evaluate JIT and free-threading — both ship off by default, so there is nothing to lose by taking them slowly.

The runtime tax of the AI era

Python is the glue language of AI and the slowest-to-start mainstream language — every agent process and data pipeline pays a cold-start tax on import. All three 3.15 headline features aim at that tax: single-digit percentage gains look modest until multiplied by agent-economy call volumes. One under-appreciated point: for the AI toolchain that treats Python as glue, lazy imports are a free lunch — no extension rebuilds, no runtime swap, annotate a few lines and collect the startup win. It is the lowest-cost, fastest-payback item in all of 3.15 — and the safest first step of any upgrade plan. Where Deno folded into Cloudflare and ceded the independent-runtime seat, Python is converting its experiments into infrastructure tenure in the same economy. Upgrade advice: run your CI against the UTF-8 default first, then flip lazy imports on gradually — separating the two steps means any failure points to a single layer.