Python 3.15, explained with short examples

Python 3.15 is out. The standard interpreter is about as fast as Python 3.14, so this release is more about useful new tools than a huge speed jump.

Here are the changes I care about, with the reason each one helps.

Lazy imports

Large Python programs often load modules they never use. That makes command-line tools feel slow before they have even started doing work.

Python 3.15 lets an import wait until the code uses it:

lazy import json
print("Ready") # json has not loaded yet
payload = json.dumps({"ok": True}) # json loads here

This is useful for CLIs with optional plugins or heavy dependencies. A small command no longer needs to load every library the full application might use. There is one catch: an import error or import-time setup now happens when the name is first used, not at startup. PEP 810 explains the details.

Easier ways to join data

Flattening nested lists used to need a double loop. The new unpacking syntax makes the intent more obvious:

groups = [[1, 2], [3, 4]]
flat = [*group for group in groups]
# [1, 2, 3, 4]

It also works for sets and dictionaries. This saves a little code, but the real benefit is that you can read “join these groups” right where it happens. PEP 798 has more examples.

A profiler in the standard library

When a service or script is slow, guessing is expensive. Python 3.15 includes a sampling profiler called Tachyon, which can attach to a process that is already running:

Terminal window
# Replace 1234 with the process ID
uv run --python 3.15 python -m profiling.sampling attach 1234

It samples the call stack while the program runs, then reports where it spent its time. That makes it easier to find the code worth fixing before you start optimizing the wrong thing.

The experimental JIT

The normal Python 3.15 interpreter is only a small step ahead of 3.14. The experimental JIT is more interesting. In Miguel Grinberg’s benchmark, it made one Fibonacci test about 20% faster and one bubble-sort test about 28% faster.

Those are promising results, but they are not a promise that every app will get faster. The JIT is still experimental, so I would treat it as something to try and measure, not a production default.

Dropping Python 3.10

Python 3.10 has reached end of life. If your project supports every maintained Python release, you can now require Python 3.11. That removes some compatibility code and unlocks useful built-ins.

For example, reading TOML no longer needs a third-party parser:

import tomllib
config = tomllib.loads('name = "my-app"')

Python 3.11 also added asyncio.TaskGroup, asyncio.timeout(), better tracebacks, and several SQLite improvements. They reduce the glue code you need in modern Python apps.

Upgrading with uv

If you use uv, the upgrade is short:

Terminal window
uv self update
uv python install 3.15
uv python pin --global 3.15

This installs 3.15 and makes it the default for uv run, unless a project selects another version.

Simon Willison’s original post is a good starting point if you want the full list of new features.

Sources