Python 3.15.0, released on October 9, 2026, uses UTF-8 for file I/O without an explicit encoding, regardless of the operating system's language and regional settings. It also introduces lazy imports, which declare modules but load them only on first use. File-handling code that relied on the operating system's encoding settings needs compatibility checks. Command-line tools that import many modules at startup may be able to reduce startup time.
1. UTF-8 is the default, not a requirement
This change follows PEP 686 and applies to standard I/O and pipes as well as file I/O without an explicit encoding. Code that specifies an encoding continues to work as before.
Most Unix environments already use UTF-8, so the impact mainly falls on Windows users. Programs relying on previous settings may display garbled text or raise errors, and in some cases silently corrupt data.
2. Find encoding dependencies and specify encodings
The check is straightforward. First, restore the previous behavior and use warnings to find places that omit an encoding. Then specify the required encoding in that code and test again with the new default. The settings are listed below.
PYTHONUTF8=0 or -X utf8=0 Disable UTF-8 mode
PYTHONWARNDEFAULTENCODING=1 or -X warn_default_encoding Enable EncodingWarning
encoding="utf-8" / encoding="locale" Specify encoding explicitlyFor files that require the operating system's configured encoding, specify encoding="locale". This value uses that encoding even when UTF-8 mode is enabled.
3. Keep declarations at the top and defer loading until first use
In PEP 810, lazy is a soft keyword with special meaning only before an import statement. An import marked lazy binds only a lightweight proxy to the name and loads the actual module when that name is first used.
Previously, a common way to defer loading heavy modules was to move import statements inside functions. The new approach keeps import declarations together at the top of the file while skipping the loading of unused modules. This feature is available only at module level; using it inside a function or exception-handling block causes a syntax error.
4. Benefits and costs shift together
Libraries that must also support older versions can declare a list of module names instead of using lazy. In 3.15, only listed modules are deferred; older versions ignore the declaration and import them immediately as usual.
__lazy_modules__ = ["json", "pathlib"]
import json # lazy
import os # still eagerA runtime option can also make all eligible top-level imports lazy, but PEP 810 advises library developers against this global control. The more unused modules there are, the greater the potential reduction in startup costs. However, loading costs, loading errors, and initialization tasks such as plugin registration for modules actually used shift to first use. Code relying on import-time side effects is safer when changed to use explicit initialization functions.
The UTF-8 default calls for reviewing existing encoding dependencies, while lazy imports call for reviewing initialization timing.
