Hyppää sisältöön

Ei vielä käännetty

Tätä sivua ei ole vielä käännetty suomeksi, joten se näytetään englanniksi. Auta kääntämään

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 import line.
  • lazy is only allowed at module scope: inside a function, a class body or a try block it is a SyntaxError.
  • Code that must also run on 3.14 and earlier can list module names in __lazy_modules__ instead. Plain import statements for those modules are lazy on 3.15; older versions ignore the variable and import eagerly.
  • -X lazy_imports=all or PYTHON_LAZY_IMPORTS=all makes ordinary imports lazy without editing the source. sys.set_lazy_imports() and sys.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.tracing is the deterministic profiler. cProfile remains as an alias of it.
  • profiling.sampling is a statistical sampling profiler that can attach to a running process.
  • The pure-Python profile module is deprecated, scheduled for removal in 3.17, and emits a DeprecationWarning on 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 bytearray buffers to bytes on 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