Skip to content
Back to blog
Development & Automation

Python 3.15 Changes: UTF-8 Default, Lazy Import, Upgrade Checks

Python 3.15 was released on October 9, 2026 with UTF-8 as the default encoding. See lazy import, frozendict, the Tachyon profiler, and what to check before upgrading.

6 min readDevelopment & Automation
Python 3.15Python new featuresPython upgradelazy importUTF-8 default encoding
Python 3.15 Changes: UTF-8 Default, Lazy Import, Upgrade Checks

Python 3.15 reached its final release on October 9, 2026, and the switch of the default encoding to UTF-8 is the change most likely to affect existing code first. It also adds lazy import, which defers module loading until first use, the immutable dictionary frozendict, and a new sampling profiler called Tachyon. Before upgrading, start by checking whether the code that opens files sets encoding explicitly (as of October 10, 2026).

According to the 3.15.0 release announcement on the official Python blog, 1,012 people contributed 5,643 commits since 3.14. Detailed behavior is covered in Python 3.15 What's New.

Python 3.15 new features at a glance

The release changes the interpreter, standard library, type hints, C API, and build. Here are the changes regular developers will meet first.

ChangeDetailsPEP
UTF-8 default encodingI/O defaults to UTF-8 regardless of the system locale686
lazy importLoads a module on first use, as in lazy import json810
frozendictBuilt-in immutable mapping type814
sentinelBuilt-in type for creating unique sentinel values661
Unpacking in comprehensionsUse * and **, as in [*L for L in lists]798
profiling package, TachyonTracing and sampling profilers in one package799
TypedDict extra items, TypeFormType hint extensions728, 747

The experimental JIT compiler also improved. The announcement says it is 7 to 8% faster (geometric mean) than the standard interpreter on x86-64 Linux, and 11 to 12% faster than the tail-calling interpreter on AArch64 macOS. The two figures use different baselines, so do not compare them side by side.

What the Python default encoding change to UTF-8 means

From 3.15, a call like open('flying-circus.txt') opens the file as UTF-8 even without encoding. The official docs say UTF-8 becomes the default regardless of the system environment (PEP 686). So existing code that opens files without encoding will read them as UTF-8 on 3.15. If a file is stored in another encoding, the result of reading it may change.

Comparison diagram showing the Python 3.15 default encoding changing from the locale encoding used up to 3.14 to UTF-8

If you need the old behavior, use the environment variable PYTHONUTF8=0 or the run option -X utf8=0. The real fix is to set encoding explicitly. Where you truly need the system locale encoding, use encoding='locale' (supported since 3.10). To find affected code, use the opt-in encoding warning described in the io module docs. On 3.10 or later, run python -X warn_default_encoding app.py. Every open() call that lacks encoding prints an EncodingWarning, so when the warnings reach zero, you have finished making encodings explicit.

PYTHONUTF8=0 python app.py
python -X utf8=0 app.py

How to use lazy import to cut startup time

lazy import (PEP 810) is syntax that adds lazy at the top of a module to defer loading until first use. The announcement says explicit lazy imports reduce startup time.

Diagram of Python 3.15 lazy import: the module is not loaded at the import line and loads on first use
lazy import json
lazy from pathlib import Path

print("Starting up...")  # json, pathlib은 아직 완전히 로딩되지 않음
data = json.loads('{"key": "value"}')  # 이 시점에 json 로딩

There are limits. It works only at module top level. Using it inside a function, a class body, or try/except/finally raises a SyntaxError. A loading failure also shows up as an exception on the line that first uses the module, not on the import line, so the location of a missing-module error changes. To apply it globally, use -X lazy_imports=all or the PYTHON_LAZY_IMPORTS environment variable. The default is normal. Code that must also run on versions before 3.15 can get the same effect with __lazy_modules__ = ["json", "pathlib"].

frozendict, sentinel, and unpacking in comprehensions

frozendict is a mapping whose values cannot be changed. It is not a subclass of dict, so code that checks types with isinstance(arg, dict) must switch to isinstance(arg, (dict, frozendict)) or use collections.abc.Mapping. If all keys and values are hashable, the frozendict is hashable too (PEP 814).

a = frozendict(x=1, y=2)
a['z'] = 3   # TypeError: 'frozendict' object does not support item assignment

Unpacking in comprehensions (PEP 798) shortens code that flattens nested lists. [*L for L in lists] gives the same result as [x for L in lists for x in L]. For sets, write {*s for s in sets}, and for dictionaries, {**d for d in dicts}. sentinel (PEP 661) is a built-in type that creates unique sentinel values with a concise representation. Its identity is preserved after copying, and you can use it in type expressions with the | operator.

The Tachyon profiler and the profiling package

PEP 799 created the profiling package. It has two parts: profiling.tracing, which records every function call (the new home of the old cProfile), and profiling.sampling (Tachyon), which samples the stack at regular intervals. cProfile stays as a compatibility alias, but the profile module is deprecated and is scheduled for removal in 3.17.

Tachyon can attach to a running process without code changes or a restart. You can pick the time basis from wall (default), cpu, and gil. It samples at up to 1,000,000Hz, and output formats include pstats, collapsed, flamegraph, gecko, and heatmap. For usage details, continue with the Tachyon section of What's New.

What to check before upgrading to Python 3.15

These items come from the official compatibility notes, ordered by how likely they are to hit real code.

CheckWhat changedWhat to do
File reads and writesDefault encoding is UTF-8Set encoding explicitly or use PYTHONUTF8=0
Use of __cached__Deprecated since 3.13 and now no longer setUse __spec__.cached
import lines in .pth filesSilently deprecatedReview the matching .start file format
tarfile extractionRejected members are not extractedCheck for missing extracted files
importlib.metadataRaises MetadataNotFound when metadata is missingAdd exception handling
argparsesuggest_on_error defaults to TrueCheck tests that depend on the error message format
profile moduleDeprecated, removal planned for 3.17Move to profiling

The announcement notes that on macOS 27.0, opening a dialog menu in IDLE or tkinter can freeze the app. The cause was reported as a behavior change in macOS 27.0, and the tracking issue is #158053. If you use that environment, test your IDLE workflow first.

Checklist for before upgrading to Python 3.15: explicit encodings, dependency compatibility, and replacing profiling tools

Start by creating a separate virtual environment with the new version and running your existing tests. Run with encoding warnings on to find calls missing encoding. Once tests pass and the warnings are cleared, move to your deployment environment. To finish the check, confirm that python --version prints 3.15.0 in the new environment and that a text file containing Korean characters reads without garbled text. Your task for today: search the project for open( and count the lines without encoding.

Frequently asked questions

Can I upgrade to Python 3.15 right away?

For a production service, run your tests in a separate environment first. Among the official compatibility notes, code that opens files without encoding, __cached__, and the tarfile extraction change are the most likely to hit existing code first.

How do I turn off the UTF-8 default change?

You can keep the old behavior with the PYTHONUTF8=0 environment variable or the -X utf8=0 option. In the long run, though, setting encoding explicitly in your code is safer across versions and operating systems.

Can I use lazy import inside a function?

No. It is allowed only at module top level. Inside a function, a class body, or a try block, it raises a SyntaxError.

Found this useful? Share it

Latest posts

Related projects

Get new posts by email

Insights on marketing, analytics, and dev, delivered to your inbox.