Python 3.15: lazy imports, frozendict, UTF-8 by default and a faster JIT

Python 3.15.0 was released on October 9, 2026. It’s one of those versions where almost every area gets something: new syntax, new built-in types, a default encoding change that had been announced for years, a sampling profiler and another step for the experimental JIT. I went through the release page and the “What’s New” document to pick out what most affects everyday code.

Explicit lazy imports

The most visible change is PEP 810, which adds explicit lazy imports, described on the release page as a way to get faster startup times. The documentation’s example:

lazy import json
lazy from pathlib import Path

print("Starting up...")  # json and pathlib are not fully imported yet

data = json.loads('{"key": "value"}')  # json gets fully imported here
p = Path(".")  # pathlib gets fully imported here

The module is only actually loaded on first use. For CLIs and tools that import a lot and use little on each run, this can shorten startup considerably. The documentation also shows global controls via sys.set_lazy_imports and sys.set_lazy_imports_filter, and compatibility with a module-level __lazy_modules__ variable, which helps libraries that need to support earlier versions.

New built-in types and syntax

Two new types land in builtins. frozendict (PEP 814) is an immutable, hashable dictionary: assigning a key raises TypeError, and two frozendict objects with the same contents have the same hash, even in a different order. You can finally use a dictionary as a key in another dictionary without workarounds.

sentinel (PEP 661) creates unique sentinel values with a readable representation. According to the documentation, they preserve identity when copied, can be used in type expressions with the | operator and can be pickled when importable by module and name. It’s the end of the classic _MISSING = object().

PEP 798 allows unpacking inside comprehensions:

>>> lists = [[1, 2], [3, 4], [5]]
>>> [*L for L in lists]
[1, 2, 3, 4, 5]

It also works with sets, dicts (with **) and generator expressions.

Another change with real impact potential: PEP 686 makes UTF-8 the default encoding. Code that opened files without specifying an encoding and relied on the system default, especially on Windows, may change behavior.

Performance and observability

The experimental JIT got a significant upgrade. According to the release page, it shows a 7% to 8% geometric mean improvement over the standard interpreter on x86-64 Linux and 11% to 12% over the tail-calling interpreter on AArch64 macOS. The official 64-bit Windows binaries now use the tail-calling interpreter.

For observability, PEP 831 turns frame pointers on by default, and PEP 799 creates the profiling package, which organizes the profilers into profiling.tracing (the former cProfile, which stays as an alias) and profiling.sampling. The latter is Tachyon, a sampling profiler that attaches to running processes without code changes or restarts, with outputs such as flamegraphs and the Firefox Profiler format. The profile module is deprecated and slated for removal in Python 3.17.

Typing, C API and caveats

On typing, PEP 728 allows TypedDict with typed extra items and PEP 747 introduces TypeForm for annotating type forms. In the C API, there’s PyBytesWriter (PEP 782) and a stable ABI for free-threaded builds (PEPs 803, 820 and 793). The official macOS installers now include free-threading support by default.

One important caveat: on macOS 27.0, IDLE and other tkinter apps may hang when opening dialogs from menus. The page attributes this to an OS behavior change affecting current Tk versions and recommends deferring the macOS upgrade if you depend on those apps, or testing first.

My take

Lazy imports and frozendict are the kind of features the community asked for over years, and they change small design decisions every day. UTF-8 by default is the change I’d test first on an older codebase, because it’s silent: no error shows up right away, only the content you read changes.

As for the JIT, the numbers are encouraging, but they still come from the project itself and from an experimental feature. I’d measure on my own workload before counting on them. And if you’re on a Mac with macOS 27, read the Tk note before upgrading the machine.

Sources