Python 3.15 Beta 1 arrived on May 7. Feature freeze is in effect — no new additions until the final release in October. What shipped includes explicit lazy imports, a production-safe sampling profiler, a measurably faster JIT, an immutable dict type, and UTF-8 encoding on by default. That last one is the one to watch. For most developers the real question is not when to upgrade, but what breaks first.
The Quiet Breaking Change: UTF-8 Is Now the Default
Python 3.15 enables UTF-8 mode by default. This sounds like a good thing — and usually is — but it silently changes behavior for any code that opens files without specifying an encoding. On Windows, the previous default was often cp1252 or whatever the system locale returned. That changes now.
If your code does this, you are at risk:
with open("config.txt") as f: # No encoding= specified
data = f.read()
On Windows, reading a file that contains characters outside ASCII can now silently produce different output or raise a UnicodeDecodeError. Fix it by being explicit:
with open("config.txt", encoding="utf-8") as f: # what you mean
data = f.read()
# Or keep the old locale behavior:
with open("config.txt", encoding="locale") as f:
data = f.read()
To find affected code before you upgrade, run your application with python -X warn_default_encoding. Any file open without an explicit encoding will throw an EncodingWarning. The new Python 3.15 What’s New docs detail every affected API.
Lazy Imports: Four Years, Two PEPs, Finally Here
PEP 810 lands explicit lazy imports. The syntax is a new lazy soft keyword: lazy import pandas. The module is not loaded at import time — a lightweight proxy takes its place. The first time you actually use pandas, the proxy resolves and loads the real module. After that, no difference.
lazy import pandas # Only loads when first accessed
lazy import matplotlib # Paid for only if used
# Or enable globally without touching source:
# PYTHON_LAZY_IMPORTS=all python myscript.py
The win is real: benchmarks on CLI tools show up to 70% startup improvement and 40% memory reduction for programs that import many modules but use only a fraction per invocation. That is every type checker, every linter, every CLI framework in the ecosystem.
The history is worth noting. PEP 690 proposed global lazy imports in 2022 and the Steering Council rejected it — the implicit, ecosystem-wide behavior change was too unpredictable. PEP 810 went the other direction: explicit opt-in only. It took longer, but it is the correct design. Languages that make this automatic (Swift, Kotlin) have different module systems. Python’s explicit approach avoids silent ecosystem surprises.
A Profiler You Will Actually Use in Production
The new profiling.sampling module brings a statistical sampler with up to 1,000,000 Hz sampling rate and under 1% overhead. It reads the call stack from outside the running process — the target application has no idea it is being profiled. You can attach it to a running process without a restart.
This matters because the existing options all have tradeoffs. cProfile instruments every function call — accurate but not safe to run in production. py-spy and Austin require elevated privileges on most systems. profiling.sampling needs neither. It is in the stdlib, requires no root access, and will not meaningfully affect your latency. The official profiling.sampling docs cover the full API.
JIT Progress: Real Numbers, Honest Caveats
The JIT compiler is 8–9% faster on x86-64 Linux and 12–13% faster on Apple silicon compared to Python 3.14. The team overhauled the tracing frontend, eliminated reference count branches, and expanded bytecode coverage by around 50%. The JIT hit its performance targets for Apple silicon a year ahead of schedule.
The honest caveat: the JIT is still off by default. You need a custom CPython build compiled with --enable-experimental-jit. For most developers, these numbers are not actionable today. The target for JIT-on-by-default is Python 3.16. Until then, the benchmarks signal strong trajectory — not a reason to rebuild your Python installation.
frozendict Comes to the Standard Library
PEP 814 adds frozendict as a built-in type. It is immutable and hashable — you can use it as a dictionary key or a set element. It does not inherit from dict, which keeps immutability guarantees clean. The third-party frozendict package has over two million downloads per month; the stdlib version replaces it for most use cases.
config = frozendict({"host": "localhost", "port": 8080})
# config["host"] = "other" # TypeError — immutable
settings = {config: "production"} # hashable — valid as dict key
What You Should Do Now
Beta 1 is the time to test, not deploy. The ABI is not locked until RC1 on August 4. Here is the practical checklist:
- Audit file open() calls — run
python -X warn_default_encodingand add explicitencoding=parameters to any flagged calls - Run your test suite against 3.15b1 —
pyenv install 3.15.0b1thenpytest - If you maintain packages — build test wheels now; publish pre-release versions so downstream can test
- For production — wait for RC1 in August 2026 before any serious migration planning
The UTF-8 default is the only change with real breakage potential. Lazy imports and the profiler are net additions. The JIT is improving but not yet invisible. Per the official beta announcement, this is also the moment for package maintainers to start building and publishing pre-release wheels — the final release is October, and finding compatibility gaps early is the whole point of the beta cycle.













