Python 3.15 arrived Thursday, October 1, 2026. The change most likely to affect existing code is the new default encoding: text I/O now uses UTF-8 regardless of system locale, so code that opens files without an explicit encoding= argument may read them differently, especially on Windows. The release also adds a lazy keyword that defers heavy imports until first use, which shortens startup for notebooks and command-line tools, and a sampling profiler in the standard library that attaches to a running process without restarting it or adding instrumentation. If your code opens files without specifying an encoding, audit it before you upgrade.
As maintainers of the Anaconda main and main-x channels, we work through what a new interpreter means for thousands of packages. Learn more about Anaconda main-x. Here is what we think matters for this release.
Adopting Python 3.15 depends on the readiness of the packages and platforms developers rely on. Our collaboration with Microsoft and Qualcomm will extend that ecosystem work to Windows Arm64, helping developers build with native Python packages on modern hardware.
“Moving Windows Arm64 into mainstream Python development requires more than a native runtime. It requires a strong package ecosystem. Anaconda has been an important partner in that work, bringing thousands of native packages to Windows Arm64 and making it easier for developers to build, test, and deploy with confidence.” —Chris Anderson, Distinguished Engineer, Windows Platform + Developer
That collaboration also connects the Python package ecosystem to the hardware developers use, including Snapdragon-powered Windows devices.
“Developers thrive when their tools just work, which is why a strong and comprehensive Python ecosystem is so important for Snapdragon X Series platforms. Working closely with Microsoft, Anaconda, and ecosystem partners, we continue to advance native support across the Python stack, making it easier for developers to build, test, and deploy applications on Windows on Snapdragon. This further enables developers to take advantage of superior performance, power efficiency, and AI capabilities of Snapdragon-powered Windows devices.” —Milan Plavsich, VP, Engineering, Qualcomm Technologies, Inc.
What changes, and what to do about it
| Change | Who it affects | What to do |
|---|---|---|
| UTF-8 is the default encoding (PEP 686) | Anyone opening files without an explicit encoding, especially on Windows | Run your tests with -X warn_default_encoding and add explicit encoding= arguments |
datetime.strptime is stricter | Date parsing that uses %d without a year directive | Add a year directive, or handle the new ValueError |
sre_compile, sre_constants, sre_parse removed | Older libraries that import CPython’s regex internals | Upgrade the dependency. There is no shim |
NamedTuple keyword syntax removed | Code that builds NamedTuple classes with keyword arguments | Switch to the class-based or functional syntax |
codeobject.co_lnotab removed | Profilers, debuggers, and coverage tools, rather than your own code | Upgrade your tooling before the interpreter |
.pth import lines deprecated (PEP 829) | Environments where packages install path hooks, including editable installs | Inventory your .pth files. Expect maintainers to migrate to .start |
| Free-threaded wheels remain scarce | Teams evaluating free-threaded Python | Profile with the new --mode gil first to find out whether it would help you |
| Lazy imports (PEP 810) | Slow-starting notebooks and command-line tools | Optional. Mark heavy top-level imports lazy, but keep anything with import-time side effects eager |
(A PEP is a Python Enhancement Proposal, the mechanism by which the language gets new features. PEP 8, the style guide, is the one most people meet first.)
Five changes that matter for data work
1. UTF-8 is now the default encoding (PEP 686)
This is the change with the widest blast radius. Opening a file without an explicit encoding used to fall back to the system locale, so the same script could read the same file differently on a Linux continuous integration (CI) runner and a colleague’s Windows laptop. In 3.15, text I/O defaults to UTF-8 regardless of environment.
Worth knowing what this touches. Anything that goes through a plain open() is affected, including pathlib, csv, and numpy.loadtxt. pandas is the exception: read_csv already defaults to UTF-8, so it behaves the same before and after 3.15.
For most people this is a correctness improvement. But if you have files genuinely encoded in something else, or code that depends on a locale default to read them, behavior changes:
# Before 3.15, the encoding came from the system locale.
# In 3.15, this reads as UTF-8 everywhere.
with open("customer_records.csv") as f:
data = f.read()
# The durable fix: always be explicit.
with open("customer_records.csv", encoding="utf-8") as f:
data = f.read()
Audit your codebase before you upgrade:
python -X warn_default_encoding -m pytest
Every warning marks a file operation whose result depends on the locale it runs under. That is exactly the code 3.15 makes deterministic. Adding explicit encoding= arguments is safe on every Python version you currently support, so you can do this today, before you upgrade anything. It is the single highest-value action in this post. If you need the old behavior meanwhile, PYTHONUTF8=0 restores locale-based encoding globally and encoding="locale" does it call by call.
2. Explicit lazy imports (PEP 810)
Anyone who has waited on a notebook kernel, or a command-line tool that imports the entire scientific stack before printing --help, knows this problem. The usual workaround is burying imports inside functions, which scatters them through the file and hides your dependencies.
Python 3.15 adds a lazy soft keyword that defers the work until the name is first used:
lazy import scipy
lazy from sklearn.ensemble import RandomForestClassifier
print("CLI ready") # neither has loaded yet
def train(X, y):
model = RandomForestClassifier() # sklearn loads here
return model.fit(X, y)
You keep every import declared at the top of the file and pay only for what a run actually uses. If a lazy module fails to load, the error surfaces at first use, with a traceback pointing at both the access site and the original import.
One caveat before you reach for this. lazy defers a module’s import-time side effects along with the import itself, so anything that registers a backend, plugin, or codec when it loads should keep a plain import. A module imported purely for its side effect, where the bound name is never referenced, will never load at all.
lazy works at module scope only, and not with star or __future__ imports. To enable it without editing source, both -X lazy_imports and PYTHON_LAZY_IMPORTS take a value: all makes every import lazy, normal is the default and respects the keyword. So it is -X lazy_imports=all, not the bare flag. Code that must also run on 3.14 can declare __lazy_modules__ instead.
3. A production-grade sampling profiler (PEP 799)
Python’s profiling tools now live in one profiling package: profiling.tracing for deterministic call tracing, relocated from cProfile, and profiling.sampling, a new statistical sampling profiler called Tachyon.
Deterministic profilers instrument every call, which both slows the program and distorts the measurement. Tachyon samples stack traces from a running process instead. The release notes describe it as having virtually zero overhead, and separately as reaching sampling rates of up to 1,000,000 Hz. You can attach to a training job already in flight, by process ID (PID), without restarting it or adding a line of instrumentation:
# Attach to a running process and write an interactive flame graph
python -m profiling.sampling attach --flamegraph -o profile.html 48213
# Is the job stuck? Dump every thread's stack
python -m profiling.sampling dump 48213
Attaching reads the target process’s memory, so it needs elevated permissions on most systems. The platform requirements cover what each OS asks for.
Four modes cover the questions data teams actually ask:
| Mode | Measures | Use it when |
|---|---|---|
--mode wall (default) | Elapsed time, including I/O and network waits | A pipeline takes an hour and you want to know why |
--mode cpu | Active CPU execution only | You need to isolate compute-bound work |
--mode gil | Time holding the interpreter lock | You are deciding whether free-threaded Python would help you |
--mode exception | Threads with an active exception | You suspect exception handling overhead |
That third row deserves emphasis. Before investing in a free-threading migration, --mode gil tells you whether lock contention is your bottleneck.
Output can be pstats tables, flame graphs, a line-level heatmap, Gecko format for the Firefox Profiler, or a live top-style terminal view. cProfile remains as an alias; the older profile module is deprecated for removal in 3.17.
4. A stable ABI for free-threaded builds (PEP 803)
Python 3.14 made free-threaded mode officially supported. C extensions targeting the stable application binary interface (ABI) had no free-threading-compatible target, so every package needed a separate build for every Python version.
Python 3.15 introduces abi3t, a stable ABI for free-threaded builds. Extensions compiled against it work with free-threaded CPython without a per-version rebuild, which is what makes broad wheel coverage sustainable rather than heroic.
Keep in mind these two caveats. Moving to abi3t is not a recompile: it needs source changes, including the PEP 697 APIs from 3.12 and the new PyModExport_* export hook from PEP 793. And as of the 3.15 release the major build backends did not yet support it, so check your tool before assuming otherwise and keep compiling cp315t separately meanwhile. CPython ships an abi3t migration guide for maintainers ready to start.
At Anaconda, we ship a free-threaded interpreter variant today, and we are working to extend free-threaded builds beyond the interpreter to packages, starting with Python 3.14 and continuing into 3.15, with no committed dates yet.
5. Startup configuration files replace .pth import lines (PEP 829)
.pth files, loaded by the site module at startup, can extend sys.path and also execute arbitrary code when a line begins with import. That second behavior is widespread: setuptools ships distutils-precedence.pth, and some editable installs do the same. It is hard to audit, because nothing tells you what runs when the interpreter starts.
Python 3.15 introduces .start files holding entry point specifications of the form pkg.mod:callable. You can read one and know exactly what it invokes. Two consequences follow:
importlines in.pthfiles are silently deprecated, without any warnings. Path extension lines are unaffected.- When a matching
.startfile exists,importlines in the corresponding.pthfile are ignored.
As maintainers migrate, an environment can hold both file types for the same package, and the .start file wins. If you run environments with custom path hooks or vendored build tooling, inventory your .pth files now and find out which ones execute code.
Also worth knowing
- The incremental garbage collector has been reverted. Python 3.14.0 through 3.14.4 shipped it; after reports of significant memory pressure in production it was reverted to the generational collector from 3.13. That is the collector in 3.14.5 and later, and in 3.15. If you saw unexplained memory growth on an early 3.14, this is likely why.
- The just-in-time (JIT) compiler has been significantly upgraded, with a new tracing frontend, basic register allocation, and better machine code generation. It is still experimental and still off by default, but it is compiled into the official builds rather than absent from them.
- Unpacking in comprehensions (PEP 798), so
[*batch for batch in batches]replacesitertools.chain.from_iterable()for flattening. frozendictandsentinelbuiltins (PEP 814, PEP 661). Note thatfrozendictdoes not subclassdict, soisinstance(x, dict)checks need updating toisinstance(x, (dict, frozendict)).ctypescomplex types changed. The_type_ofc_float_complex,c_double_complex, andc_longdouble_complexchanged fromF,D, andGtoZf,Zd, andZg, for NumPy compatibility.- Frame pointers on by default (PEP 831), plus better
AttributeErrorsuggestions and color across the standard library command-line interfaces (CLIs). - Further removals. Beyond those in the table above:
ctypes.SetPointerType,glob.glob0andglob.glob1,pathlib.PurePath.is_reserved,platform.java_ver, and zero-fieldTypedDictconstruction.typing.ByteStringandcollections.abc.ByteStringleave their modules’__all__ahead of full removal in 3.17. The full list is in the release notes.
When to move
| Posture | Timing | Best for |
|---|---|---|
| Conservative | 6 to 12 months out, so April to October 2027. Stay on 3.13 or 3.14 and test in isolation meanwhile | Production systems, mission-critical applications, complex dependency graphs |
| Early adopter | Two to four months. Test the core scientific stack first, keep a fallback environment | Research teams, individual developers, experimental projects |
| Bleeding edge | Now, with 3.15.0 final in an isolated environment | Package maintainers, core contributors |
Python 3.10 reaches end of support
Anaconda is ending support for Python 3.10 at the end of October 2026, matching the Python Software Foundation (PSF)’s five-year window. After that date, 3.10 interpreter and variant packages remain available in the main channel, but neither the PSF nor Anaconda will ship further patch or security releases, and we stop building new 3.10 variant packages.
A 3.10 environment keeps working. It stops receiving security fixes while the ecosystem around it moves on. Details are in our Python version support policy.
Getting started
The migration rewards preparation over speed. Audit your encodings now, test in isolated environments, and give the ecosystem time before you move production. Packages with compiled code can’t be built reliably until the interpreter’s internals stop changing, and that only happened eight weeks before release.
Our support policy provides our expected cadence, so you can plan against it rather than guess: once the PSF makes an official release, we typically publish the interpreter to the main channel within two weeks, the first packages within a month, and the majority of remaining packages within four months. We expect Python 3.15 will follow that pattern.
If you maintain packages, add 3.15 to your CI matrix and keep your conda-forge feedstock current. One note that is easy to miss: if your build system compiles C, C++, or Rust without inheriting Python’s sysconfig flags, enable the frame-pointer flags yourself, because a single component built without them breaks stack unwinding for the whole process.
Download Anaconda Distribution or Miniconda to set up your test environments.
Questions about Python 3.15 compatibility? Reach out to our support team or start a thread in our community forums.