Python 3.15 is the October 2026 release, and its headline feature is one the community has wanted for a decade: lazy imports, so a module’s import cost is paid only when it is actually used. Alongside that come a statistical profiler in the standard library, a noticeably faster experimental JIT, UTF-8 as the default text encoding everywhere, and another round of clearer error messages. Here is what each one means for code you write.
Lazy imports#
A large application can spend a second or more at start-up importing modules that a given run never touches. Command-line tools feel this most: importing pandas to print --help. 3.15 adds explicit lazy imports:
lazy import pandas
lazy from matplotlib import pyplot
def main(args):
if args.plot:
pyplot.plot(...) # matplotlib is imported here, on first use
The name is bound immediately, but the module is loaded the first time the name is used. If it is never used, it is never loaded. Start-up time drops to what the code actually needs.
There is also a global switch, -X lazy_imports or the PYTHON_LAZY_IMPORTS environment variable, that makes ordinary top-level imports lazy without changing source. That is useful for measuring the gain before committing to the syntax, but be careful with it: modules that rely on import-time side effects, such as registering plugins, may behave differently.
A sampling profiler in the standard library#
cProfile records every function call, which slows a program down enough to change what you are measuring. The new profiling.sampling module takes snapshots of the call stack at intervals instead, costing almost nothing:
python -m profiling.sampling script.py
python -m profiling.sampling --pid 12345 # attach to a running process
Attaching to a live process is the important part. You can profile a production service for thirty seconds and see where the time goes, then detach. The old profiler is still there under the name profiling.tracing; cProfile continues to work as an alias.
UTF-8 by default#
Since 3.7 there has been a UTF-8 mode you could opt into. In 3.15 it is on by default: open() without an encoding argument reads and writes UTF-8 on every platform, rather than the operating system’s locale encoding. On macOS and Linux that changes nothing. On Windows, where the locale encoding was typically a legacy code page, it changes quite a lot.
# 3.14 on Windows: reads as cp1252 (or similar); accented characters may be mangled
# 3.15 everywhere: reads as UTF-8
with open("data.txt") as f:
text = f.read()
Scripts that read files written by older Windows software in a legacy encoding will now need encoding="cp1252" spelled out. The upside is that the same script finally behaves the same on every machine. If you already pass encoding= everywhere, as linters have recommended for years, nothing changes.
Faster, without changes#
The experimental JIT compiler, still opt-in via a build flag, is measurably quicker than in 3.14 — the release notes cite high single-digit to low double-digit percentage gains on standard benchmarks, depending on platform. The regular interpreter also picked up improvements. You get these by upgrading; there is nothing to enable for the interpreter gains.
Free-threaded builds, which run without the global interpreter lock, continue to mature. They remain a separate build rather than the default, so unless you install one deliberately you are still running with the GIL.
Better error messages#
Each recent release has made tracebacks more helpful, and 3.15 continues: more “did you mean” suggestions, clearer messages for common mistakes with strings, arguments and unpacking, and better pointing at the exact expression that failed. These are small individually and add up to less time staring at a stack trace.
Smaller changes#
- Continued clean-up of long-deprecated behaviour: several functions and parameters scheduled for removal in 3.15 are now gone. Run your tests on 3.14 with
-W error::DeprecationWarningto find them before upgrading. - Type-annotation improvements building on 3.14’s deferred evaluation of annotations.
- Standard-library additions and performance work in
asyncio,pathliband the JSON module.
Should you upgrade?#
Wait for the first patch release, 3.15.1, before moving production. Libraries with compiled components need a few weeks after a release to publish wheels; installing on the day of release means building from source. For new projects starting in late 2026, 3.15 is a fine target. For existing ones, the checklist:
- Run the test suite on 3.14 with deprecation warnings as errors.
- Check that every compiled dependency has a 3.15 wheel:
pip download package --only-binary :all: --python-version 3.15. - On Windows, search for
open(calls without anencodingargument that read legacy files. - Upgrade in a fresh virtual environment and run the tests again.
Questions people ask#
When is Python 3.15 released?
The final release is scheduled for early October 2026, following the usual annual cycle. Release candidates are available before that for testing.
Will my 3.14 code run on 3.15?
Almost certainly, unless it relies on something deprecated for removal or on the old Windows default encoding. The test-with-warnings-as-errors step finds both.
Is the JIT on by default now?
No. It is still experimental and enabled at build time. Most installations, including the official installers, run the standard interpreter.
Do lazy imports work with from-imports?
Yes: lazy from module import name. Star imports cannot be lazy, because the names are not known until the module loads.
Where to go next#
- Python 3.14’s improved error messages — the previous round.
- uv vs pip — the fastest way to try a new Python version.
- Bytes, strings and encodings — why the UTF-8 default matters.