Python 3.15 shipped today. The headline is not the JIT numbers or the new built-ins — it is the removal list. datetime.utcnow() is gone. importlib.resources.read_text() is gone. UTF-8 is now the default encoding on every platform, including Windows. If your codebase has not been tested against 3.15, this is the day to find out what breaks.
What Just Broke
Python 3.15 completes deprecations that were announced years ago. None of these are surprises in the release notes, but they will surface as runtime errors the moment you upgrade:
datetime.utcnow()is removed. The replacement isdatetime.now(datetime.UTC). This catches a lot of codebases that have been ignoring the deprecation warning for two releases.importlib.resources.read_text()andread_binary()are gone. Call.read_text()and.read_bytes()on theTraversableobject returned byimportlib.resources.files().glob.glob0()andglob1()— removed. Useglob.glob().__cached__module attribute — removed. Access__spec__.cachedinstead.- Internal
sre_*modules — removed. Any code importing these directly will break immediately.
The biggest runtime risk in 3.15 is not a new feature — it is a 10-year-old function that stopped working today. Run your test suite before deploying.
The Encoding Change Is Live
PEP 686 is now in effect. Every open() call that omits encoding= now reads and writes UTF-8, regardless of system locale. On macOS and Linux this rarely matters — UTF-8 was already common. On Windows, where the default code page is typically cp1252 or a regional variant, this is a behavior change that produces silent data corruption or UnicodeDecodeError on files you have been reading for years.
Before upgrading any Windows deployment, run this one command:
PYTHONWARNDEFAULTENCODING=1 python -m pytest
Each warning points to an open() call missing an explicit encoding. The fix is one of two lines depending on what the file contains: encoding="utf-8" for config files, JSON, and network data; encoding="locale" for files that must match whatever encoding the user’s system expects. If you are on a large codebase, pyupgrade will handle the mechanical part automatically.
The JIT: Should You Enable It?
The JIT compiler in 3.15 is still opt-in. Enable it with PYTHON_JIT=1 or the -X jit flag. Whether it helps you depends almost entirely on what your code does.
On the pyperformance benchmark suite: 8–9% geometric mean speedup on x86-64 Linux, 12–13% on Apple Silicon. For tight numerical loops and ETL pipelines, real-world numbers reach 20–40%. For I/O-bound workloads or subprocess-heavy code, the JIT adds nothing — and in a handful of pathological cases, costs 10–20%.
The 3.15 JIT adds register allocation, a new tracing front end, and reference count elimination for certain object classes — genuinely new work over the 3.14 version. The opt-in flag costs you nothing to try.
frozendict and sentinel
Two new built-in types land with no import required. frozendict is an immutable, hashable mapping — finally baked into the language after years of third-party packages filling the gap. The practical cases: function defaults (eliminates the mutable default argument trap), cache keys (a frozendict can be a dictionary key when all its values are hashable), and configuration objects that must not be mutated after construction.
key = frozendict(user_id=42, region="us-east")
cache[key] = fetch_data(key) # frozendict is hashable
sentinel("NAME") creates unique sentinel values with better type-checker support than None. Use it anywhere you need a “no value provided” signal that None cannot express unambiguously. Both types are supported by json, pickle, and copy natively.
Tachyon: Profile a Running Process
The new profiling module ships with Tachyon, a statistical sampler that can attach to a running process by PID — no restart required. Sampling rates up to one million Hz, flame graph output, async-aware mode. cProfile still works as a backwards-compatible alias; the profile module is deprecated with removal planned for 3.17.
The practical difference from cProfile: you can attach Tachyon to a production process for a few seconds, capture a flame graph, and detach — without stopping anything. That alone makes it worth knowing.
Upgrading Today
Three commands before you push any 3.15 upgrade:
PYTHONWARNDEFAULTENCODING=1 python -m pytest # find encoding= gaps
pyupgrade --py315-plus **/*.py # auto-fix open() calls
PYTHON_JIT=1 python -m pytest # benchmark JIT on your code
Python 3.15 also ships lazy imports (covered in depth in our PEP 810 post) and the free-threading abi3t stable ABI (covered when RC2 shipped). The summary: python3.15 still has the GIL; python3.15t does not. The removals, the encoding change, and the JIT are what you need to act on today. The full release notes are at the official What’s New in Python 3.15 page.













