Python 3.15: What's New and How to Prepare
Python 3.15 beta 4 ships tomorrow, 18 July. Release candidate 1 follows on 4 August, and the final release lands on 1 October 2026. The feature set has been frozen since May, which means the interesting question is no longer what might make it in. It is what you should be testing over the next eleven weeks.
This is a bigger release than 3.14. It adds a new keyword, changes a default that has been locale-dependent since Python’s beginning, ships a production-grade sampling profiler in the standard library, and quietly reverts one of 3.14’s headline changes. Here is what matters, verified against the official What’s New in Python 3.15 ↗ document.
The Release Schedule
The dates come from PEP 790, the official Python 3.15 release schedule ↗:
| Milestone | Date |
|---|---|
| Beta 1 (feature freeze) | 7 May 2026 |
| Beta 4 | 18 July 2026 |
| Release candidate 1 | 4 August 2026 |
| Release candidate 2 | 1 September 2026 |
| Final release | 1 October 2026 |
| Bugfix support ends | ~October 2028 |
| Security support ends | ~October 2031 |
RC1 is the traditional signal for library maintainers to publish compatible wheels. If you maintain a package, that is your deadline for a 3.15-tagged release; if you consume packages, early October is when you find out which of your dependencies missed it.
Lazy Imports: A New Keyword
PEP 810 is the headline. Python gains a lazy soft keyword that defers a module import until the name is first used:
lazy import json
lazy from pathlib import Path
print("Starting up...") # neither module loaded yet
data = json.loads('{"key": "value"}') # json loads here
p = Path(".") # pathlib loads here
The motivation is startup time. Large applications and CLI tools routinely import hundreds of modules at startup when any given invocation touches a fraction of them. Teams have hacked around this for years with function-level imports and importlib tricks; lazy makes the intent explicit and the mechanics reliable. If an import fails, the error is raised at first use with a traceback pointing back to the original import statement.
The restrictions are sensible: lazy only works at module scope, and neither from module import * nor __future__ imports can be lazy. For codebases you cannot edit, -X lazy_imports and the PYTHON_LAZY_IMPORTS environment variable can force all imports lazy globally, with sys.set_lazy_imports() available for programmatic control.
One caution: import-time side effects are load-bearing in more codebases than anyone admits. Registering plugins, opening connections, patching modules. Flipping the global switch on an application that depends on import order is a debugging afternoon waiting to happen. Adopt the keyword deliberately, module by module, rather than forcing it everywhere on day one.
UTF-8 Becomes the Default Encoding
PEP 686 finally lands: open('notes.txt') with no encoding argument now uses UTF-8 on every platform, independent of the system locale.
On Linux and macOS, where the locale encoding is almost always UTF-8 already, most code will not notice. Windows is the real target. The previous default there was a legacy code page such as cp1252, which means code that writes files on one machine and reads them on another has been silently corrupting non-ASCII text for decades. That entire bug class dies in 3.15.
The migration risk runs the other way: files previously written in a legacy code page will now be read as UTF-8 and may fail or produce mojibake. Two things to do before October:
- Run your suite once with
-X warn_default_encoding. The opt-inEncodingWarningflags every I/O call that relies on the default. - Fix the flagged sites by passing
encoding="utf-8"(or the correct legacy encoding for old data files) explicitly. Explicit encoding works identically on 3.14 and 3.15, so you can ship the fix today.
PYTHONUTF8=0 restores the old behaviour as an escape hatch, but treat it as a rollback mechanism, not a plan.
Tachyon: A Sampling Profiler in the Standard Library
PEP 799 reorganises profiling under a new profiling package: profiling.tracing is the deterministic tracer relocated from cProfile (which survives as an alias), and profiling.sampling is Tachyon, a new statistical sampling profiler.
Tachyon is the significant half. It attaches to a running Python process, no restart, no code changes, and samples stack traces at rates up to 1,000,000 Hz with near-zero overhead. That is the workflow py-spy pioneered, now in the standard library and maintained with the interpreter. For diagnosing a production process that is on fire right now, a zero-dependency python -m profiling.sampling is a genuinely big deal.
The old profile module is deprecated and will be removed in Python 3.17. Nobody profiling seriously has used it in years; if it appears anywhere in your tooling, swap it out.
PEP 831 supports the same observability story: frame pointers are now enabled by default on supported platforms, so system-level profilers like perf produce usable Python stack traces without special builds. The runtime cost was measured as negligible.
The JIT Gets Real Numbers
The JIT compiler was rebuilt around LLVM 21 with a new tracing frontend and basic register allocation. The reported results, explicitly marked as not yet final: an 8 to 9 percent geometric mean improvement on the pyperformance suite versus the standard interpreter on x86-64 Linux, and 12 to 13 percent over the tail-calling interpreter on AArch64 macOS.
The spread matters more than the mean: individual benchmarks range from roughly 15 percent slower to more than twice as fast. If you are considering a JIT build, run your own workload before drawing conclusions. Our post on why developer productivity matters more than you think makes the broader argument, but the short version applies here: measure, do not assume.
Free Threading Grows Up: abi3t
PEP 803 defines a stable ABI for free-threaded builds, known as abi3t. Until now, every C extension needed a separate wheel per Python version for free-threaded CPython, which is a large part of why no-GIL adoption has been slow. With abi3t, maintainers can ship one wheel that works across free-threaded releases.
Porting is not free; extensions need the PEP 697 APIs and the new PyModExport hook rather than PyInit_. But this is the piece that makes free-threaded Python practical to support for the long tail of packages. If your team has been waiting for the no-GIL ecosystem to mature before experimenting, 3.15 is the release where the foundation stops moving.
Smaller Changes Worth Knowing
frozendictis a built-in (PEP 814): a hashable, immutable mapping that preserves insertion order and is not adictsubclass. Config objects and dict keys just got cleaner.- A
sentinelbuilt-in (PEP 661) replaces the module-level_MISSING = object()idiom, with proper repr, pickling and type-expression support. - Unpacking in comprehensions (PEP 798):
[*chunk for chunk in chunks]now flattens directly. re.prefixmatch()arrives as the explicit name forre.match(), which is soft deprecated. Nothing breaks, but new code should say what it means.tomllibsupports TOML 1.1: trailing commas and newlines in inline tables, at last.- The incremental GC is gone. Python 3.14 shipped a new incremental garbage collector; after reports of significant memory pressure in production it was reverted to the generational GC in 3.14.5, and 3.15 keeps the generational collector. If you skipped early 3.14 point releases over memory concerns, this is the resolution.
What to Do Before October
- Add 3.15 to your CI matrix now. Test against beta 4, then RC1 from 4 August. Promote
DeprecationWarningto an error in the test environment so nothing hides. If your pipeline makes that painful, fix the pipeline first: our guide to speeding up CI builds covers keeping a multi-version matrix fast. - Hunt encoding bugs. One CI run with
-X warn_default_encoding, then add explicitencodingarguments everywhere it fires. This is the change most likely to bite Windows users and cross-platform file handling. - Audit your dependencies. Check that the C extensions you rely on publish 3.15 wheels at RC1, and pin accordingly rather than discovering gaps on release day. A sane process for this is covered in dependency management without the chaos.
- Do not enable global lazy imports in production yet. Adopt the
lazykeyword incrementally where startup time hurts, and let the ecosystem shake out the import-side-effect bugs first.
Python releases stopped being scary years ago; the annual cadence and two-year bugfix window have made upgrades routine. But 3.15 changes a default encoding, adds a keyword and swaps GC strategy versus early 3.14, which is more surface area than usual. Eleven weeks is plenty of time to test. It is not plenty of time to start testing in week ten.
Frequently asked questions
When is Python 3.15 released?
The final release is scheduled for 1 October 2026. Release candidate 1 lands on 4 August 2026 and RC2 on 1 September 2026. The feature set has been frozen since beta 1 on 7 May 2026, so what is in the betas now is what ships. Python 3.15 will receive bugfix releases for roughly two years and security fixes until around October 2031.
What are lazy imports in Python 3.15?
PEP 810 adds a lazy soft keyword: writing lazy import json defers loading the module until the first time the name is actually used. It only works at module scope, and star imports and __future__ imports cannot be lazy. You can also force lazy behaviour globally with the -X lazy_imports command-line option or the PYTHON_LAZY_IMPORTS environment variable. The payoff is faster startup for CLIs and large applications that import far more than any single code path uses.
Does Python 3.15 change the default file encoding?
Yes. Under PEP 686, open() and other I/O calls without an explicit encoding argument now use UTF-8 everywhere, regardless of the system locale. On Linux and macOS this changes little, but on Windows the previous default was a legacy code page, so code that round-trips files without naming an encoding can start reading old files incorrectly. You can opt out with PYTHONUTF8=0, but the right fix is to pass encoding explicitly.
How much faster is Python 3.15?
The what's new page reports an 8 to 9 percent geometric mean improvement on the pyperformance suite for the JIT over the standard interpreter on x86-64 Linux, and 12 to 13 percent over the tail-calling interpreter on AArch64 macOS, with the caveat that the numbers are not yet final. Individual workloads range from a roughly 15 percent slowdown to over 100 percent speedup, so benchmark your own code rather than assuming the mean.
What should I test before Python 3.15 is released?
Run your test suite against the current beta or RC1 in CI, with warnings promoted to errors so deprecations surface. Run once with -X warn_default_encoding to find file operations relying on the old locale-based default encoding. If you ship C extensions, check whether your dependencies build against the new abi3t stable ABI. If you replaced the profile module years ago you are fine; if not, note that it is deprecated and gone in 3.17.
Enjoyed this article? Get more developer tips straight to your inbox.
Comments
Join the conversation. Share your experience or ask a question below.
No comments yet. Be the first to share your thoughts.