Python 3.15 Beta 4 landed July 18, and it is the last one. RC1 is 13 days away (August 4), the general release ships October 1, and the feature list is now locked. If your project depends on Python and you haven’t run your test suite against 3.15 yet, this is the window. What you see in Beta 4 is what ships in October. The official announcement lists roughly 298 bugfixes and improvements over Beta 3.
Lazy Imports: The Startup Tax Is Optional Now
PEP 810 ships in 3.15 and it is genuinely significant. The syntax is a single new keyword:
lazy import numpy as np
lazy import pandas as pd
# Neither module loads until this line runs
arr = np.array([1, 2, 3])
Until a lazy-imported module’s attribute is first accessed, it sits in sys.lazy_modules rather than sys.modules — a proxy object, not a loaded module. Meta reported a 70% reduction in CLI initialization time after converting their internal libraries. For any tool with a heavy import graph, the cold-start improvement is substantial.
Two things to know before you start slapping lazy on every import. First, lazy imports must be at module level — lazy import inside a function is a syntax error. Second, import-time side effects are deferred. Any library that registers signals, patches globals, or initializes logging on import will not do that work until its first attribute is accessed. If your codebase relies on import-order side effects, this will silently break that assumption. Modules can declare __lazy_modules__ = False to opt out. The full PEP 810 covers edge cases in detail.
UTF-8 Default: The Windows Breaking Change Nobody Is Talking About
PEP 686 makes UTF-8 the default encoding for all I/O on all platforms. On Linux and modern macOS this changes nothing. On Windows, it changes everything.
Before 3.15, open("file.txt") on Windows used the system locale encoding — typically CP1252 or Latin-1. Starting in 3.15, it uses UTF-8. Any code that reads or writes text files on Windows without an explicit encoding= argument is now implicitly trusting UTF-8. If those files were written with the old locale encoding, you get mojibake or a UnicodeDecodeError.
The fix is explicit: always pass encoding= when locale matters, or set PYTHONUTF8=0 to preserve legacy behavior. There is a useful side effect for those already standardized on UTF-8: pyupgrade’s new --py315-plus flag will strip redundant encoding="utf-8" arguments from open() calls since UTF-8 is now the default. Read the PEP 686 rationale for the full compatibility story.
frozendict Is Now a Builtin — Check Your Dependencies
PEP 814 adds frozendict to the builtins. Creation syntax is identical to dict:
config = frozendict(host="localhost", port=5432, debug=False)
# config["debug"] = True # TypeError
hash(config) # works, if all values are hashable
frozendict is not a subclass of dict — it inherits from object. The json, pickle, and copy modules have been updated to handle it. The more pressing issue: the popular frozendict package on PyPI name-collides with the new builtin. Code using from frozendict import frozendict or import frozendict has a naming conflict. Audit your dependencies before upgrading, and update isinstance(x, dict) checks to isinstance(x, (dict, frozendict)) or isinstance(x, collections.abc.Mapping) where appropriate.
Tachyon Profiler and the sentinel Builtin
The new profiling stdlib package reorganizes Python’s profiling tools. profiling.tracing houses the old cProfile (the cProfile alias still works). profiling.sampling is Tachyon — a statistical sampling profiler with near-zero overhead compared to cProfile’s function-call tracing approach.
The headline capability: Tachyon can attach to a running Python process without requiring a restart or code change:
python -m profiling.sampling attach 12345
Output formats include flamegraph HTML, Firefox Profiler, a live TUI, and pstats. It supports async functions, free-threaded builds, and multi-threaded code. The profile module is deprecated in 3.15 and scheduled for removal in Python 3.17. Start migrating away from it now.
The sentinel builtin (PEP 661) standardizes a pattern Python developers have hand-rolled for years:
MISSING = sentinel("MISSING")
def fetch(key, default=MISSING):
result = cache.get(key)
return result if result is not MISSING else compute(key)
Sentinel objects are named, picklable, preserve identity through copy and deepcopy, and work with | in type hints. Pallets/click has already opened an issue to migrate their sentinel usage.
Test Beta 4 Today
Install via pyenv (pyenv install 3.15.0b4), the official download page, or the python:3.15-rc Docker image. Run your test suite, watch for UTF-8 surprises on Windows, audit your frozendict imports, and check any library you intend to lazy-import for side-effect dependencies. Found an issue? File it at bugs.python.org — RC1 is August 4 and that is the last practical window to get fixes into 3.15.

