未翻訳のページ
このページはまだ日本語に翻訳されていないため、英語の原文を表示しています。 翻訳に協力する
Python 3.15 - Complexity & Optimizations¶
Python 3.15 was released October 2026 with lazy imports, two new built-in types, and C implementations of the Base32 and Base85 codecs.
From 3.15 the Python documentation has its own time complexity page for the built-in types. It covers list, tuple, dict and frozendict, set and frozenset, str, bytes and bytearray, memoryview and range.
New Features Affecting Complexity¶
Explicit Lazy Imports (PEP 810)¶
The new lazy soft keyword defers loading a module until the imported name is first used:
lazy import json
import sys
assert "json" not in sys.modules # nothing located, read or executed yet
data = json.loads('{"key": "value"}') # first use imports json
assert "json" in sys.modules
assert type(globals()["json"]) is type(sys) # the name now holds the module
| Operation | Time | Space | Notes |
|---|---|---|---|
lazy import m / lazy from m import x |
O(1) | O(1) | Binds a proxy; the module is not located or run |
| First use of a lazily bound name | Cost of a normal import | Same as a normal import | Replaces the proxy in the module's globals with the real object |
| Later uses | O(1) | O(1) | An ordinary global lookup |
- A failed import surfaces at first use, not at the
lazy importline. lazyis only allowed at module scope: inside a function, a class body or atryblock it is aSyntaxError.- Code that must also run on 3.14 and earlier can list module names in
__lazy_modules__instead. Plainimportstatements for those modules are lazy on 3.15; older versions ignore the variable and import eagerly. -X lazy_imports=allorPYTHON_LAZY_IMPORTS=allmakes ordinary imports lazy without editing the source.sys.set_lazy_imports()andsys.set_lazy_imports_filter()control the same thing at run time; see the sys module.
frozendict (PEP 814)¶
frozendict is an immutable mapping built on the same hash table as dict. It is hashable when all of its keys and values are, so it can be a dict key or a set member. It is not a subclass of dict.
config = frozendict(host="localhost", port=8080)
assert config["port"] == 8080 # O(1) average
cache = {config: "connection"} # usable as a key
assert hash(config) == hash(frozendict(port=8080, host="localhost"))
assert config.copy() is config # O(1): no copy needed
| Operation | Time | Space | Notes |
|---|---|---|---|
frozendict(mapping) |
O(n) | O(n) | Copies the items; a frozendict argument is returned as is |
fd[key], key in fd |
O(1) avg | O(1) | Same lookup as dict |
hash(fd) |
O(n) first call, O(1) after | O(1) | The hash is cached on the object |
fd.copy() |
O(1) | O(1) | Returns the same object |
See frozendict for the full reference.
sentinel (PEP 661)¶
sentinel(name) creates a unique marker object with a readable repr, replacing the MISSING = object() idiom:
import copy
MISSING = sentinel("MISSING")
def lookup(mapping, key, default=MISSING):
value = mapping.get(key, MISSING)
if value is MISSING:
if default is MISSING:
raise KeyError(key)
return default
return value
assert lookup({"a": 1}, "a") == 1
assert lookup({}, "a", 0) == 0
assert copy.deepcopy(MISSING) is MISSING # identity survives copying
Copying a sentinel returns the sentinel itself, so is comparisons still hold after copy.copy() or copy.deepcopy() of a structure that contains one. See sentinel.
Unpacking in Comprehensions (PEP 798)¶
* and ** can now be used in list, set and dict comprehensions and in generator expressions:
lists = [[1, 2], [3, 4], [5]]
assert [*row for row in lists] == [1, 2, 3, 4, 5]
dicts = [{"a": 1}, {"b": 2}, {"a": 3}]
assert {**d for d in dicts} == {"a": 3, "b": 2} # later keys win
flat = (*row for row in lists) # lazy, like any generator expression
assert next(flat) == 1
| Form | Time | Space | Notes |
|---|---|---|---|
[*it for it in its] |
O(N + M) | O(N) | N = total items, M = number of inner iterables |
{*it for it in its} |
O(N + M) avg | O(N) | One hash insertion per item |
{**d for d in ds} |
O(N + M) avg | O(K) | K = distinct keys in the result |
(*it for it in its) |
O(N + M) in total | O(1) | Takes the next inner iterable only when the current one is exhausted |
Each form has the same bound as the nested comprehension it replaces, such as [x for it in its for x in it], and the list form the same as list(itertools.chain.from_iterable(its)), so the choice between them is one of readability.
bytearray.take_bytes()¶
take_bytes() turns a bytearray into bytes by handing over its buffer instead of copying it:
buffer = bytearray(b"header:")
buffer += b"payload"
data = buffer.take_bytes()
assert data == b"header:payload"
assert len(buffer) == 0 # the bytes object now owns the storage
| Operation | Time | Space | Notes |
|---|---|---|---|
buf.take_bytes() |
O(1) for a buffer with no spare capacity | O(1) | The new bytes takes over the buffer and buf is left empty. A buffer grown by appends has spare capacity, which is first trimmed with realloc, at the allocator's cost |
bytes(buf) |
O(n) | O(n) | Copies; buf is unchanged |
take_bytes(n) takes only the first n bytes and leaves the rest in the buffer; see bytearray for its cost.
profiling Package (PEP 799)¶
The new profiling package holds the standard library's profilers:
profiling.tracingis the deterministic profiler.cProfileremains as an alias of it.profiling.samplingis a statistical sampling profiler that can attach to a running process.- The pure-Python
profilemodule is deprecated, scheduled for removal in 3.17, and emits aDeprecationWarningon import.
import cProfile
import profiling.tracing
assert cProfile.Profile is profiling.tracing.Profile # same profiler
math.integer¶
The new math.integer module groups the integer functions. comb, factorial, gcd, isqrt, lcm and perm are the same function objects as the math names, so either spelling has the same cost.
re.prefixmatch()¶
re.prefixmatch() and Pattern.prefixmatch() are new names for re.match() and Pattern.match(), which are now soft deprecated. re.prefixmatch is the same function object as re.match; switching names changes nothing about cost.
Behaviour Changes¶
UTF-8 Is the Default Encoding (PEP 686)¶
UTF-8 mode is on by default, whatever the locale, so open() without an encoding argument uses UTF-8:
import sys
assert sys.flags.utf8_mode == 1 # unless disabled with -X utf8=0 or PYTHONUTF8=0
Pass encoding="locale" to keep the locale's encoding, or an explicit encoding to behave the same on every version.
Performance Improvements¶
Base32 and Base85 in C¶
The Base32, Ascii85, Base85 and Z85 codecs in base64 now call C functions in binascii instead of running Python loops:
| Operation | Time | Space | Notes |
|---|---|---|---|
b85encode, a85encode, z85encode |
O(n) | O(n) | Only the output is allocated |
b85decode, a85decode, z85decode |
O(n) | O(n) | Only the output is allocated |
b32encode, b32decode, b32hex* |
O(n) | O(n) | Only the output is allocated |
The bounds do not change, but on 3.14 and earlier the Base85 family built Python-level intermediates many times the size of the output. See the base64 module.
subprocess.Popen.wait() with a Timeout¶
wait(timeout=...) now blocks on a pidfd with poll() on Linux 5.3+ and on kqueue on macOS and the BSDs, instead of polling with sleeps that back off to 50 ms. Windows already waited on the process handle. Where none of these is available, the sleep loop remains. See the subprocess module.
Garbage Collection¶
3.15 uses the generational garbage collector, as 3.13 did. The incremental collector that shipped in 3.14.0 through 3.14.4 was reverted in 3.14.5 after reports of memory pressure. See the gc module.
JIT Compiler¶
The experimental JIT compiler is substantially upgraded but remains off by default, enabled with PYTHON_JIT=1 on builds that include it. No bound on this site depends on it.
Removed APIs¶
| Removed | Use instead |
|---|---|
sre_compile, sre_constants, sre_parse |
re |
glob.glob0(), glob.glob1() |
glob.glob() with root_dir |
http.server.CGIHTTPRequestHandler |
- |
pathlib.PurePath.is_reserved() |
os.path.isreserved() (Windows) |
platform.java_ver() |
- |
codeobject.co_lnotab |
codeobject.co_lines() |
importlib.abc.Loader.load_module(), zipimport.zipimporter.load_module() |
exec_module() |
wave getmark(), setmark(), getmarkers() |
- |
typing.no_type_check_decorator |
- |
ctypes.SetPointerType() |
- |
When to Upgrade¶
Upgrade to 3.15 if:
- Start-up time matters and many imports are only sometimes used (lazy imports)
- You need an immutable, hashable mapping (
frozendict) - You encode or decode Base32 or Base85 data
- You convert
bytearraybuffers tobyteson a hot path
Check before upgrading:
- Text files read or written without an explicit
encoding - Imports of the removed modules and functions above
End of Life¶
- Release: October 2026
- Bugfix Support: About two years, until around the release of 3.17
- Security Support: Until October 2031