Python 3.15 makes two hidden assumptions worth checking
UTF-8 becomes the default and explicit lazy imports can defer work until first use. Data compatibility and startup optimization need separate migration plans.
Python 3.15's most consequential change may be a text file that stops opening correctly. The stable release, published on October 9, makes UTF-8 the default text encoding while also introducing explicit lazy imports. One change reduces a long-standing source of differences between machines; the other lets developers decide which imports can wait until they are needed.
The release announcement contains more conspicuous additions, but these two deserve attention together. Both alter assumptions that can remain invisible in a successful test run: how bytes become text, and when importing a module executes its code. A runtime upgrade can expose those assumptions without changing the application's main algorithm.
The encoding assumption becomes visible
An encoding is the agreement that maps stored bytes to characters. UTF-8 provides a common way to represent text across languages. Historically, Python programs that omitted an encoding could behave differently depending on the platform's locale settings. A file handled correctly on one developer's machine might fail, or produce incorrect characters, elsewhere.
As PEP 686 explains, Python 3.15 enables UTF-8 mode by default. The benefit is a more predictable baseline. The compatibility problem is older data whose actual encoding does not match that baseline. Changing Python's default does not convert the contents of an existing export, log or archived document.
For a file whose format specifies UTF-8, make that agreement visible:
with open("catalogue.txt", encoding="utf-8") as catalogue:
text = catalogue.read()
The important part is the explicit contract, rather than those particular characters. A legacy file that genuinely uses another encoding needs its correct encoding specified instead. Adding encoding="utf-8" everywhere without checking the data would simply replace an implicit mistake with an explicit one.
PEP 686 also documents the temporary PYTHONUTF8=0 opt-out and the use of encoding warnings to locate implicit assumptions. An opt-out can help isolate a regression; it does not establish a durable format for data exchanged between systems. The maintainable fix is to know what each boundary reads and writes.
Lazy imports move work to first use
PEP 810 introduces explicit syntax for deferring an import. Ordinary imports retain their existing behavior. A developer can instead write:
lazy import json
def read_settings(raw):
return json.loads(raw)
Here, the module is loaded when the program first needs it. That can help a command-line tool whose many subcommands use different dependencies: a user invoking one small operation need not necessarily pay the startup cost of modules used only by other operations.
The cost has moved, however. The first operation that needs the dependency may take longer, and an import-time error can surface later in execution. Code that depends on a module's top-level side effects also needs careful consideration. Making imports lazy indiscriminately changes when work happens, which is part of program behavior even when the final calculation is unchanged.
Measure the path users actually invoke. A faster empty startup followed immediately by the same deferred loading is less useful than avoiding that loading altogether on a common command. Likewise, tests should exercise the first use of a deferred dependency, not merely confirm that the application starts.
Upgrade the contract, then the optimization
These changes reward a deliberate order of work. First establish that files, subprocess text and other data boundaries still mean what the application expects. Then introduce lazy imports where a measured startup cost justifies them. Combining both migrations in one broad rewrite would make a failure harder to attribute.
Python 3.15 is part of the continuing open-source software coverage, but its useful lesson extends beyond one release: defaults are part of an application's environment. Making them more consistent can reveal old dependencies on inconsistency. Explicit encodings make those dependencies easier to remove; explicit lazy imports make execution timing a choice developers can inspect and test.