Reference · Python versions · Preview

Preview: Python 3.15

Python 3.15 was released in October 2026. This course is built on Python 3.14, so everything here is a preview: what changes when you move up, how to try it today with Pixi, and what to watch for when you migrate.

⏱ ~50 min3.15 preview relative to the 3.14 baselineOfficial notes: What's New In Python 3.15

How to try it with Pixi

You don't need to change your project to experiment. pixi exec creates a temporary, cached environment from a spec and runs a command in it:

# a throwaway 3.15 REPL
pixi exec --spec "python=3.15" python

# run one file with 3.15
pixi exec --spec "python=3.15" python lazy_demo.py

# check the version
pixi exec --spec "python=3.15" python -VV

To test a project against 3.15 alongside its 3.14 default, add a feature and an environment to pixi.toml. The default environment keeps Python 3.14; the py315 environment uses 3.15.

[dependencies]            # shared by every environment; no python pin here
pytest = "*"

[feature.py314.dependencies]
python = "3.14.*"

[feature.py315.dependencies]
python = "3.15.*"

[environments]
default = ["py314"]       # the course baseline
py315 = ["py315"]         # the preview
pixi install -e py315               # solve and install the 3.15 environment
pixi run -e py315 python -VV         # Python 3.15.x
pixi run -e py315 pytest             # your test suite on 3.15
pixi shell -e py315                  # a shell with 3.15 active

Explicit lazy imports (PEP 810)

A normal import runs the whole module right away. Large CLIs often import dozens of heavy modules they never use in a given run, so --help takes a second. The new lazy soft keyword binds the name immediately but defers the actual import until the name is first used.

Before (3.14): import inside functions

def export(rows):
    import json        # hidden dependency
    return json.dumps(rows)

After (3.15): lazy at module level

lazy import json

def export(rows):
    return json.dumps(rows)

frozendict built-in (PEP 814)

Python had frozenset but no immutable mapping in the builtins. frozendict is a read-only, hashable dict: it can be a dict key, a set member, a safe default value, or a constant configuration table that nobody can mutate by accident.

Before (3.14):

from types import MappingProxyType
DEFAULTS = MappingProxyType({"retries": 3})
# read-only view, but not hashable

After (3.15):

DEFAULTS = frozendict(retries=3)
cache[DEFAULTS] = "ok"   # hashable

Taught alongside immutability in M18 Functional Python.

sentinel built-in (PEP 661)

A sentinel is a unique marker value meaning "nothing was passed", used when None is itself a valid argument. The old idiom _MISSING = object() has an ugly repr (<object object at 0x...>) and does not survive copying or pickling well.

Before (3.14):

_MISSING = object()

def get(key, default=_MISSING): ...

After (3.15):

MISSING = sentinel("MISSING")

def get(key, default=MISSING): ...

UTF-8 is the default encoding (PEP 686)

Until now, open(path) without encoding= used the locale encoding, which is UTF-8 on macOS and Linux but often cp1252 on Windows. The same script could read a file correctly on one machine and produce mojibake (or UnicodeDecodeError) on another. In 3.15 UTF-8 mode is on by default, so text I/O defaults to UTF-8 everywhere.

Unpacking in comprehensions (PEP 798)

You can now use * and ** directly in a comprehension's output expression to flatten one level. It reads exactly like the unpacking you already use in list and dict displays.

Before (3.14):

flat = [x for xs in lists for x in xs]
merged = {k: v for d in dicts for k, v in d.items()}

After (3.15):

flat = [*xs for xs in lists]
merged = {**d for d in dicts}

The profiling package (PEP 799)

Profilers get a single home. profiling.tracing is the deterministic profiler you know as cProfile (which remains as an alias). profiling.sampling is new: the "Tachyon" sampling profiler, which periodically records the stack of a running process from the outside. Sampling has almost no overhead, so you can point it at a production process, and it can produce flame graphs or a live, top-like view.

# deterministic, function-level counts (formerly cProfile)
pixi run -e py315 python -m profiling.tracing -s cumtime app.py

# sample a script and write an interactive flame graph
pixi run -e py315 python -m profiling.sampling run --flamegraph -o profile.html app.py

# attach to a running process and watch it live
pixi run -e py315 python -m profiling.sampling attach --live 12345

The old pure-Python profile module is deprecated (see migration). M39 teaches profiling workflow on 3.14 with cProfile.

math.integer (PEP 791)

The integer-only functions of math get their own module: math.integer holds gcd, lcm, isqrt, comb, perm, and factorial. Keeping them separate from the floating-point functions makes it clear which results are exact. The old names in math continue to work.

See M34 Strings, Bits & Number Theory.

Smaller library improvements

TOML 1.1 in tomllib

tomllib now parses TOML 1.1: inline tables may span several lines and end with a trailing comma, the \e and \xHH escapes are allowed, and the seconds in times are optional.

Counter symmetric difference

Counter gains ^: for each key, the absolute difference of the counts.

asyncio.TaskGroup.cancel()

A task group can now be cancelled as a whole, which stops every task in it and exits the async with block cleanly. Before, you had to raise a custom exception and catch it outside.

Better "did you mean" suggestions

AttributeError suggestions now know common names from other languages, so [].push(1) suggests .append.

>>> [].push(1)
AttributeError: 'list' object has no attribute 'push'. Did you mean '.append'?

Typing

PEPFeatureWhat it lets you say
728TypedDict(closed=True), extra_items=T"This dict has exactly these keys" or "extra keys are allowed, with values of type T".
747typing.TypeFormA parameter that accepts a type expression such as int | None, not just a class.
800Disjoint bases (@typing.disjoint_base)Two classes can never have a common subclass, which lets checkers narrow more precisely.

Typing on 3.14 is taught in M16. Type checkers adopt new PEPs on their own schedule; check your mypy version's release notes.

Interpreter and performance

Upgraded JIT

The copy-and-patch JIT introduced experimentally in 3.13/3.14 is significantly upgraded and gives real speedups on many benchmarks. Benchmark your own workload with and without PYTHON_JIT=1.

Frame pointers by default (PEP 831)

CPython is built with frame pointers, so system profilers (perf, eBPF tools, Instruments) can walk Python and C stacks cheaply and accurately.

Generational GC restored

3.14's incremental collector is reverted in favor of the classic generational one after it caused memory growth in some workloads.

pixi exec --spec "python=3.15" python -c "import sys; print(sys._jit.is_available())"
PYTHON_JIT=1 pixi run -e py315 python bench.py

Migration notes: 3.14 → 3.15

ChangeRiskWhat to do
UTF-8 is the default text encodingPrograms on Windows that relied on cp1252 (or another locale encoding) read/write files differently.Pass encoding= explicitly everywhere. Use encoding="locale" where you truly need the old behavior; PYTHONUTF8=0 as a temporary escape hatch.
Generational GC replaces incremental GCPause times and memory profile change in programs with big heaps; code tuned with gc.set_threshold for 3.14 may behave differently.Re-measure peak memory and latency with your real workload before switching production.
profile module deprecatedDeprecationWarning on import; scheduled for removal in 3.17.Switch to profiling.tracing (or cProfile, which works on both versions).
import lines in .pth files deprecated (PEP 829)Packages that run startup code via .pth import lines emit warnings.Ship a <name>.start file instead, listing pkg.mod:callable entry points; plain path lines in .pth files are still fine.
New soft keyword lazyVery low: soft keywords stay valid as names.None, but avoid naming variables lazy in new code for readability.
Third-party wheels and conda packagesCompiled packages need 3.15 builds.Solve a py315 Pixi environment early; the lockfile shows exactly which packages are available.

Upgrade checklist

Check yourself

Flashcards and further reading

Going further