Python 3.15.0 Ships With Lazy Imports, frozendict, and a Faster Interpreter
Python 3.15.0 is now available. The core team at the Python Software Foundation confirmed the final build dropped on today, making it the latest major release in the language's history and the direct successor to 3.14.
Unlike the usual incremental bugfix releases, this one marks the start of a fresh development line. The team built it from 5,643 commits by 1,012 contributors, a number that says as much about the size of the community as it does about a release process that has quietly become one of the most predictable in open source.
The theme here is developer experience and performance, roughly in that order. The headline additions, like explicit lazy imports, a first-class immutable dictionary, a real sentinel type, and UTF-8 as the default text encoding, all chase the same petty daily frustrations. Speed gains show up in a substantially beefed-up experimental JIT, a tail-calling interpreter for Windows, and free-threading turned on by default for macOS.
What actually changed
The cadence still follows PEP 602: one feature release a year, two years of bugfix patches, and security-only updates through roughly October 2031. Hugo van Kemenade is release manager again this cycle, with Steve Dower on Windows installers and Ned Deily on macOS. The team even signed off from "Salutations de Rennes," a nod to the European Python conference where coordination happened this year.
Development started May 7, 2025. Alpha 1 landed in mid-October 2025, feature freeze hit at Beta 1 in May 2026, Candidate 3 on October 2, and the final build this week. That 17-month stretch is deliberate, designed to keep feature versions a year apart while still giving you two years of maintenance and three more years of security fixes.
The biggest feature most people will notice is lazy imports. PEP 810 adds a lazy soft keyword that defers actually loading a module until you first use it.
lazy import json
lazy from pathlib import Path
print("Starting up...") # neither module is fully loaded yet
You still write imports at the top of the file, so everything stays readable. You just stop paying the compile-and-load tax for code you never touch on a given run. If a lazily imported module fails to load, Python raises the error at the point of first use rather than at import time, and the traceback shows both where you hit the problem and where the import lives.
You can also go global with the -X lazy_imports flag or PYTHON_LAZY_IMPORTS environment variable, or get finicky with runtime hooks like sys.set_lazy_imports_filter() to make only your own modules lazy. The proxy object behind a lazy import shows up as types.LazyImportType.
Then there's frozendict, which has probably been requested more times than anyone cares to count. PEP 814 finally ships a built-in immutable, insertion-ordered dictionary that is also hashable, as long as its keys and values are.
>>> a = frozendict(x=1, y=2) >>> a['z'] = 3 TypeError: 'frozendict' object does not support item assignment
It doesn't subclass dict, inheriting straight from object, so you can't mutate it through dict methods. The hashability is the fun part. You can now drop a frozendict into a set or use it as a dictionary key, and pass it straight to @functools.lru_cache(). It also plays nice with copy, json, pickle, decimal, and a few others.
The sentinel builtin from PEP 661 fixes a different kind of annoyance. Everyone who has written Python long enough has defined _missing = object() somewhere, mostly to tell "no value given" apart from an actual None. That idiom is clunky: the repr is ugly, it has no useful type, and copies can break identity checks.
>>> MISSING = sentinel('MISSING')
>>> MISSING
MISSING
Sentinel objects keep their identity across copies and even across pickling when importable by module and name. Jelle Zijlstra contributed it, Tal Einat authored the PEP, and it came after a genuinely long community poll on discuss.python.org, so it isn't landing unilaterally from the top.
For UTF-8 as the default encoding (PEP 686), the practical upshot is that this now mostly affects Windows users, since most Unix systems already ship UTF-8 locales. An open('flying-circus.txt') without an explicit encoding argument now uses UTF-8, matching what Node, Go, Rust, and Java have done for years. You can force the old behavior with PYTHONUTF8=0, but the core team's advice hasn't changed: pass an explicit encoding and move on.
Smaller but welcome changes include unpacking inside comprehensions (PEP 798), which lets you write [*L for L in lists] instead of the nested loop equivalent. There are also .start files (PEP 829), a cleaner and more auditable alternative to the code-executing import lines in .pth files, which are now deprecated.
The speed stuff
Performance is where 3.15 has been quietly working. The experimental JIT got a real upgrade. Based on pyperformance numbers reported at doesjitgobrrr.com, you're looking at a 7 to 8 percent geometric mean improvement over stock CPython on x86-64 Linux, and 11 to 12 percent over the tail-calling interpreter on AArch64 macOS.
The range is wide, some benchmarks run 15 percent slower, others break 100 percent faster, which is exactly what you'd expect from a still-experimental, opt-in profiler. It's not production-ready acceleration yet, but the trajectory is steady.
Windows binaries now use the tail-calling interpreter, made possible by a feature in Visual Studio 2026 (MSVC 18). That buys 15 to 20 percent on the same geometric mean, with gains up to 40 percent on long-running small pure-Python scripts. macOS binaries install free-threading support by default now, which is a bit of a trip given how hard the core team warned everyone about GIL-free builds during the 3.13 cycle. At this point, they're basically shoving it down our throats, and honestly, it's fine.
Error messages got sharper too. Ask Python for a nonexistent attribute and it might suggest something reachable through a nested member. Type in a JavaScript method like .push() and it'll ask, "Did you mean '.append'?" It even checks a table of method names from Java, Ruby, and C#. Small things, but the ones that make you grumble at your terminal every single day.
On the profiling side, PEP 799 folds everything into a single profiling package. The older cProfile sticks around as a backwards-compatible alias, while a new sampling profiler called Tachyon does deterministic-free work at rates up to 1,000,000 Hz. It can attach to a running process by PID, dump a snapshot of a hung one, and output flame graphs, heatmaps, or a live top-like TUI. The pure-Python profile module is deprecated and set to disappear in 3.17.
For extension authors, there's a Stable ABI for free-threaded builds (abi3t), a new PyBytesWriter C API, and interpreter guards that protect against finalization problems. The old PyGILState_* family is soft-deprecated, not yanked.
One aside, before you install
Before we get to where you grab it, there's a small thing worth pausing on. Barry Warsaw, a longtime core contributor and former PSF president, built a text-adventure game called "whatsnewt" to celebrate. You play through the interpreter, solving eighteen puzzles, each of which forces you to actually use a real 3.15 feature. Some of them need a type checker, verified by pyrefly.
The whole thing is a clever way to learn the new builtins without reading release notes, and it comes complete with easter eggs. If nothing else, this release has personality.
To play it, the recommended one-liner is:
uvx --python 3.15 whatsnewt
Where to get it
Head here to download the release from python.org. You find the full documentation here, and the dedicated "What's New in Python 3.15" page covers everything change-by-change.
It's arguably the most useful Python release in a couple of years for day-to-day coding, friendlier errors, real builtins for patterns we've all hand-rolled for a decade, and genuinely faster interpreters. Whether it's ready for your production stack depends entirely on how much you care about UTF-8 defaults and old-style sqlite calls. Either way, the install button is green, and the long beta wait is finally over.
