What's New in Python 3.15
Python 3.15.0 was released 10 days ago, and I have been going through EVERY change, here they all are!
Language Features
Explicit Lazy Imports
Imports can be deferred until the imported name is actually used.
lazy import json
lazy from pathlib import Path
print("started") # Neither module needed yet
json.loads("{}") # json loads here
Path("file.txt") # pathlib loads here
Lazy imports can also be enabled globally:
python -X lazy_imports=all app.py
Useful for reducing startup time in applications with large dependency trees.
Unpacking in Comprehensions
* and ** can now be used directly inside comprehensions.
You'd expect this to already have been the case, but no!
lists = [[1, 2], [3, 4], [5]]
flat = [*xs for xs in lists]
# [1, 2, 3, 4, 5]
frozendict
Python now has a built-in immutable dictionary to match frozenset.
config = frozendict(
host="localhost",
port=8080,
)
config["port"] = 3000
# TypeError
A frozendict can be hashable if its contents are hashable:
cache = {
frozendict(x=1, y=2): "result"
}
sentinel
Python now has a standard way to create sentinel values.
Before:
MISSING = object()
Python 3.15:
MISSING = sentinel("MISSING")
def get(key, default=MISSING):
if default is MISSING:
...
Sentinels have a useful representation, preserve their identity when copied, support pickling, and integrate with typing, which is SUPER useful! No need anymore to write None sentinals or object() or type with Never.
UTF-8 by Default
UTF-8 is now Python's default text encoding regardless of the operating system locale. This has been the cause of 1000s of headaches for me, so I'm super happy about this! I hated writing encoding='utf-8' over and over and over.
with open("data.txt") as f:
text = f.read()
Effectively defaults to:
open("data.txt", encoding="utf-8")
The system locale encoding can still be explicitly requested because it's Python:
open("data.txt", encoding="locale")
Package Startup .start Files
.start files provide a safer replacement for executable import lines inside .pth files.
mypackage.start
mypackage.startup:initialize
Python resolves and executes the specified startup entry point.
Executable import lines in .pth files are now deprecated.
If anyone is using these, PLEASE let me know?? I've never seen this used anywhere.
Better Error Messages
Python provides more contextual suggestions for common mistakes by c++ developers :)
[1, 2, 3].push(4)
May suggest:
Did you mean '.append'?
Likewise:
"hello".toUpperCase()
can suggest:
.upper()
Python can also suggest nested attributes when an attribute is accessed on the wrong object.
bytearray.take_bytes()
Bytes can be moved out of a bytearray without copying them.
Before:
data = bytes(buffer)
buffer.clear()
Now:
data = buffer.take_bytes()
Or consume only part:
header = buffer.take_bytes(16)
Useful for network parsers, compression, buffering, and binary protocols.
More Flexible __slots__
__dict__ and __weakref__ slots can be defined more generally, and tuple subclasses gain additional __slots__ support.
class Point(tuple):
__slots__ = ("metadata",)
Generic slice
slice is now subscriptable for typing.
def select(s: slice[int]) -> None:
...
Unary + in Pattern Matching
Positive numeric literals can explicitly use + in match.
match value:
case +10:
print("ten")
Profiling
New profiling Package
Python's profiling tools now live under a common namespace.
profiling.tracing
profiling.sampling
cProfile remains available for backwards compatibility, while profile is deprecated.
Tachyon Sampling Profiler
Python now ships a high-frequency statistical profiler. Woah!
Profile a program:
python -m profiling.sampling run app.py
Attach to an existing process:
python -m profiling.sampling attach 12345
Generate a flame graph:
python -m profiling.sampling run --flamegraph app.py
Tachyon supports:
- Wall-clock profiling
- CPU profiling
- GIL profiling
- Exception profiling
- Threads
- Async tasks
- Flame graphs
- Heatmaps
- Live TUI output
- Opcode profiling
- Very high sampling frequencies
- What this is a crazy list of features!!
Typing
TypedDict Extra Items
TypedDict can explicitly control whether additional keys are allowed.
Closed dictionary:
class User(TypedDict, closed=True):
name: str
age: int
Extra keys become type errors:
user: User = {
"name": "TJ",
"age": 20,
"favorite_color": "red", # Error
}
Extra keys can also be allowed with a specific type (extremely awesome):
class Scores(TypedDict, extra_items=int):
name: str
scores: Scores = {
"name": "TJ",
"math": 95,
"english": 100,
}
TypeForm
TypeForm[T] represents a type expression itself. We are getting meta now!
def check[T](value: object, typ: TypeForm[T]) -> T:
...
Allows type expressions such as:
check(x, int)
check(x, list[str])
check(x, str | None)
Primarily useful for type-aware libraries and frameworks.
Disjoint Bases
Type checkers can understand when classes can never share subclasses.
Standard Library
math.integer
A new module contains mathematical operations specifically designed for integers.
from math.integer import *
This separates integer-specific algorithms from the primarily floating-point-oriented math API.
asyncio.TaskGroup.cancel()
Task groups can now be explicitly cancelled.
async with asyncio.TaskGroup() as tg:
tg.create_task(worker())
tg.cancel()
Previously, early termination commonly required deliberately raising and suppressing an exception which was annoying.
Concurrent Iterators
threading adds utilities for safely working with iterators concurrently.
threading.synchronized_iterator(...)
threading.concurrent_tee(...)
Better argparse
Argument suggestions are now enabled by default, as they always should have been.
$ app.py --ouput file.txt
Did you mean '--output'?
BooleanOptionalAction also gains broader option-prefix support.
Base Encoding Improvements
base64 and binascii gain improvements including:
- Better padding handling
- Canonical validation
- Line wrapping
- Ignored-character handling
- Expanded Base32 support
- Expanded Base85 support
- Expanded Z85 support
For example:
base64.b64decode(data, canonical=True)
can reject non-canonical encodings.
More Colored CLI Output
Colored output has been expanded across the standard library. 3.14 did a bunch of this, but now it is much more consistant!
This includes tools such as:
argparse
ast
calendar
difflib
http.server
sqlite3
timeit
tokenize
unittest
zipfile
python --help
The interactive shell also colors tab completions by object type and improves completion for from ... import ....
Performance
Faster Base Encoding
CPython's Base64 implementation is approximately:
Encoding: ~2x faster
Decoding: ~3x faster
Base32 and Base85-family operations can see dramatically larger improvements after implementations were moved to C. :(
Faster CSV Sniffing (never seen this before)
csv.Sniffer().sniff(data)
Delimiter detection can be around 1.6x faster.
Improved Experimental JIT
Python's experimental JIT received major improvements.
Approximate official pyperformance results:
x86-64 Linux: +7-8%
AArch64 macOS: +11-12%
Individual workloads range from regressions to more than 2x speedups, so nothing real has happened yet. One day.
mimalloc for Free-Threaded Raw Allocations
Free-threaded builds now use mimalloc for raw memory allocation, improving performance.
Free-Threaded Python
Stable ABI for Free-Threaded Extensions: abi3t
C extensions can now target a stable ABI specifically for free-threaded CPython.
abi3 -> Normal Stable ABI
abi3t -> Free-threaded Stable ABI
cp315t -> Python 3.15-specific free-threaded ABI
This allows extension authors to support future free-threaded Python versions without necessarily compiling a separate binary for every release.
macOS Free-Threading Support
Official macOS installers now install free-threading support by default.
Observability and Native Performance
Frame Pointers Enabled by Default
CPython enables frame pointers by default on supported platforms.
This improves stack unwinding for tools such as perf and GDB.
Tail-Calling Interpreter on Windows
Official 64-bit Windows builds now use CPython's tail-calling interpreter. (YAY!)
Import System
Module locks are now acquired hierarchically.
This fixes a class of multithreaded import deadlocks involving related packages.
For example, concurrent imports of:
import pkg.sub
and:
import pkg.sub.mod
are safer when package initialization itself imports submodules.
Garbage Collection
Python 3.15 returns to the generational garbage collector.
The incremental garbage collector introduced in Python 3.14 was reverted after reports of significant memory pressure in some production workloads.
Removals and Deprecations
re.match() Soft Deprecation
New code is encouraged to use:
re.prefixmatch(...)
instead of:
re.match(...)
The new name makes the operation's behavior clearer: it only matches at the beginning of the string.
re.match() is not currently planned for removal.