
Python 3.15.0 shipped on October 9, 2026, a few days late after RC3 got held up by a lazy-import bug. Five changes are worth actually understanding instead of skimming past: explicit lazy imports, a real UTF-8 default, unpacking inside comprehensions, a standard sentinel type, and a new sampling profiler. Here's what each one changes about the code you write, with working examples for all of them.
1. Explicit lazy imports (PEP 810)
Python has always imported a module's entire dependency tree the moment you hit the import line, even if that module is only used behind a rarely-taken branch. PEP 810 adds a lazy soft keyword that defers loading until the name is actually touched. It only works at module scope — not inside functions, classes, or try blocks — because the parser needs to statically verify the import at compile time.
# report_tool.py
import sys
import json
lazy import pandas as pd # not loaded yet
lazy from pathlib import Path # not loaded yet
def basic_status():
return {"ok": True, "cwd": str(Path.cwd())} # Path loads here, on first touch
def generate_report(csv_path):
df = pd.read_csv(csv_path) # pandas (and numpy under it) loads here
return df.describe().to_dict()
if __name__ == "__main__":
if "--report" in sys.argv:
print(json.dumps(generate_report(sys.argv[-1])))
else:
print(json.dumps(basic_status()))Run report_tool.py with no flags and pandas (plus its import chain) never loads at all — only Path does, the moment basic_status() calls it. Run it with --report data.csv and pandas loads right when generate_report first references pd. The binding exists immediately either way; only the actual module load is deferred.
Two restrictions worth remembering: star imports and __future__ imports can't be marked lazy, and because lazy is a soft keyword (only reserved directly before import/from), existing code using lazy as a variable name keeps working untouched.
2. UTF-8 is now the actual default (PEP 686)
This one is a cross-platform correctness fix disguised as a boring encoding change. Before 3.15, open() without an explicit encoding= argument used UTF-8 on Linux/macOS but the legacy ANSI code page on Windows. A file written on one platform and read on another could silently mangle non-ASCII text.
with open("notes.txt", "w") as f:
f.write("café résumé 北京")
# Python ≤3.14 on Windows: written as cp1252/cp936/whatever the system locale is
# Python 3.15+, any platform: written as UTF-8, full stop
with open("notes.txt") as f:
print(f.read())
# now guaranteed to round-trip correctly everywhere, with zero encoding= boilerplateIf you were already passing encoding="utf-8" everywhere as a defensive habit (the right call pre-3.15), nothing changes for you. If you weren't, this quietly closes a whole class of "works on my machine" bug reports from Windows users.
3. Unpacking inside comprehensions (PEP 798)
Flattening a list of lists used to mean either a nested for clause or reaching for itertools.chain.from_iterable. PEP 798 lets you unpack directly in the comprehension's output expression:
batches = [[1, 2, 3], [4, 5], [6]]
# old way
flat_old = [x for batch in batches for x in batch]
# new way — unpack right where you'd unpack a literal
flat_new = [*batch for batch in batches]
print(flat_new) # [1, 2, 3, 4, 5, 6]It generalizes the unpacking you already use in literals ([*a, *b], {*a, *b}) to comprehensions, rather than introducing new behavior. The Steering Council's one condition on accepting the PEP: don't market it as "more readable" than the nested-for version — it's offered for syntactic consistency, not as a replacement in every case. Use whichever reads clearer for the specific flatten you're doing.
4. A standard sentinel type (PEP 661)
Every codebase eventually invents its own "this isn't None, it means genuinely missing" marker — usually a one-off object() or a hand-rolled class. PEP 661 standardizes that pattern:
from sentinellib import Sentinel
MISSING = Sentinel("MISSING")
def get_config(key, default=MISSING):
value = _lookup_in_config_file(key)
if value is MISSING:
return default
return value
print(get_config("timeout")) # falls back to MISSING, returns MISSING itself
print(get_config("timeout") is MISSING) # True — the only reliable way to checkThe important behavioral detail: check with is, not ==. Sentinels compare equal only to themselves by identity, they can't be subclassed, and calling Sentinel("MISSING") again in the same module returns the same object rather than creating a new one — so you get stable, debuggable, repr()-friendly markers instead of a bare object() that prints as <object object at 0x...>.
5. Tachyon: a real sampling profiler in the standard library (PEP 799)
PEP 799 reorganizes the profiling story: cProfile moves (with a compatibility alias) to profiling.tracing, the old profile module is deprecated for removal in 3.17, and a new statistical sampling profiler — nicknamed Tachyon — ships as profiling.sampling.
The difference that matters: cProfile instruments every call, which adds real overhead and can distort timing on hot paths. A sampling profiler instead periodically snapshots the call stack of a running process, so overhead stays close to zero — which is also why it can attach to a process that's already running, not just one it launches:
# run and profile a script directly
python -m profiling.sampling run my_script.py
# or attach to something already running, by PID — no restart needed
python -m profiling.sampling attach 48213
# take a single stack snapshot without a full profiling session
python -m profiling.sampling dump 48213Reach for this instead of cProfile whenever you need to profile something already running in production, where instrumentation-level overhead isn't acceptable.
Try it yourself
The lazy-import change is the one most worth adopting first — it's a one-word change (lazy import instead of import) on any module-level import that's only needed behind a conditional path, a CLI flag, or an optional feature, and it costs nothing when that path isn't taken. Go find the heaviest, least-often-used import in your own project and try it.

Experienced DevOps Engineer proficient in Python, Docker, Kubernetes, CI/CD, and cloud technologies. Specialized in automating processes and building scalable infrastructure for seamless development and deployment.


