Python 3.15 Upgrade Guide: Install with uv, Use Lazy Imports, and Check Your Data Stack
Updated on

The short answer: Python 3.15.0 was released on October 9, 2026. If you use uv (opens in a new tab), upgrade uv first, then install:
uv self update
uv python install 3.15Then make a test project and try your real dependencies before you move anything important:
uv init --python 3.15 py315-test
cd py315-test
uv add numpy pandas scikit-learn matplotlibThe core scientific stack installs from ready-made wheels today. PyTorch and TensorFlow do not. DuckDB compiles from source. If your project depends on any of those, stay on 3.14 for now (uv python pin 3.14).
The feature worth upgrading for is lazy imports. One keyword, lazy import pandas as pd, took a test script's --help path from 2.7 seconds to 0.01 seconds.
What was tested: everything marked "tested" below was run on October 11, 2026, on an Apple M4 Max (macOS) with uv 0.13.0 and CPython 3.15.0 / 3.15.0t from
uv python install. Windows and Linux behavior, the python.org installer, pyenv, and conda come from the official docs and were not tested.
What changed in 3.15 that you will notice
The changelog is long. If you write data scripts or CLIs, these five changes are the ones you will run into:
| Change | What it means for you | Action |
|---|---|---|
| Lazy imports (PEP 810) | lazy import x defers loading until first use | Use it in CLIs, scripts, and slow-starting apps |
| UTF-8 is the default encoding (PEP 686) | open("file.txt") now reads and writes UTF-8 on every OS | Windows users with cp1252 / cp932 / GBK files: pass encoding= explicitly |
| uv 0.13 defaults to 3.15 | New uv projects with no pin get 3.15 automatically | Pin 3.14 where your dependencies are not ready |
| Free-threaded build gets a stable ABI (PEP 803) | python3.15t runs threads in parallel, but most packages do not ship stable-ABI wheels for it yet | Expect source builds on 3.15t |
frozendict, sentinel built-ins, profiling package | New built-in types and the Tachyon sampling profiler | Optional; nothing breaks if you ignore them |
The full list is in the official What's New in Python 3.15 (opens in a new tab). The rest of this guide covers the tasks: install, check compatibility, use lazy imports, handle the encoding change, and decide on free-threading.
- Python 3.15 Upgrade Guide: Install with uv, Use Lazy Imports, and Check Your Data Stack
- Claude Code Mods Tutorial: Build and Test Your First Mod, Plus 15 Community Examples
- How to Use Pi Coding Agent 1.0: Install, Connect a Model, and Run Your First Task
- How to Use Laya: Classify Text in a pandas DataFrame with the Open-Source Jev Alternative
- Claude Code Reads AGENTS.md Now: Setup, Loading Modes, and Sharing One File with Codex, OpenCode, and Cursor
- How to View Deleted Reddit Posts (2026 Guide): 5 Ways That Still Work
- GPT Image 2.5: How to Use It, Flare vs Sunburst, and API Pricing
- How to Use DeepSeek Harness: Install, Set Up, and Run Your First Agent
- Runcell Science: An Open Source Alternative to Claude Science for Research Workflows
- How to Make Mac Not Sleep: Keep Codex, Claude Code, and AI Agents Running
- OpenClaw vs ZeroClaw vs Pi Agent vs Nanobot: Which AI Agent Stack Should You Choose in 2026?
- Can Claude Code Analyze Jupyter Notebooks for Data Science? What It Actually Does
- Claude Code Routines: Why AI Agent Cron Jobs Matter
- Claude Code Desktop Bypass Permissions: How to Enable It
- How to Build Two Python Agents with Google’s A2A Protocol - Step by Step Tutorial
- Top 10 growing data visualization libraries in Python in 2025
Step 1: Install Python 3.15
With uv (tested)
Update uv before you install. Skipping this is the easiest mistake to make this week. An older uv only knows the alpha builds. On the uv 0.9.25 we already had installed, uv python list 3.15 showed only cpython-3.15.0a4. Running uv python install 3.15 there would install an alpha, not the release.
uv self update # needs 0.13.0 or newer
uv --version
uv python install 3.15uv 0.13.0 installed both the regular and free-threaded builds in 24.8 seconds:
uv python install 3.15 3.15tInstalled 2 versions in 24.83s
+ cpython-3.15.0+freethreaded-macos-aarch64-none (python3.15t)
+ cpython-3.15.0-macos-aarch64-none (python3.15)If you installed uv with Homebrew, pip, or pipx, uv self update does not work. Upgrade it the same way you installed it (brew upgrade uv, pipx upgrade uv).
uv 0.13 also changes the default. The uv 0.13.0 release notes (opens in a new tab) say that uv python install with no version now downloads 3.15. A new uv venv or uvx python downloads 3.15 if no compatible Python is already installed. Existing installs are still used. To keep a project on 3.14, record it:
uv python pin 3.14That writes .python-version, which uv reads every time it runs in that folder.
Other ways (from official docs, not tested here)
- python.org installer (Windows, macOS): download 3.15.0 from python.org/downloads (opens in a new tab). According to the release notes, the official macOS installer now installs free-threading support by default, and the Windows 64-bit binaries use the new tail-calling interpreter.
- pyenv:
pyenv update, thenpyenv install 3.15.0. Wait until your pyenv version lists 3.15.0. - conda: create an environment with
python=3.15once conda-forge has published it. Check before you plan a migration around it.
If you want the general picture of switching Python versions on each OS, our older guide to upgrading Python and the Python version manager comparison still apply. This page covers what is specific to 3.15.
Step 2: Check whether your packages install
This is the step that decides whether you can upgrade today. We created a clean 3.15 project and installed a typical data-science stack, then checked PyPI for the packages people ask about most.
Compatibility table (tested October 11, 2026)
| Package (version) | Python 3.15 | Python 3.15t (free-threaded) | Notes |
|---|---|---|---|
| numpy 2.5.4 | Wheel | Wheel | |
| pandas 3.0.6 | Wheel | Wheel | |
| scipy 1.18.1 | Wheel | Wheel | |
| scikit-learn 1.9.1 | Wheel | Wheel | |
| matplotlib 3.11.2 | Wheel | Wheel | |
| pyarrow 26.0.0 | Wheel | Wheel | |
| polars 2.0.0 | Wheel (abi3) | Builds from source | Its runtime ships only cp310-abi3 wheels, which free-threaded Python cannot use. The Rust build took 6 min 51 s |
| ipykernel 9.17.1 (pyzmq, psutil, debugpy) | Wheel | Not tested | Jupyter kernels work on 3.15 |
| duckdb 1.5.6 | Builds from source | Not tested | Wheels stop at cp314. The C++ build took about 2.5 min |
| pygwalker 0.5.0.1 | Works | Not tested | Pulls duckdb, so it inherits the source build |
| torch 2.14.1 | Cannot install | Cannot install | Wheels exist only for cp310 to cp314 and cp314t |
| tensorflow 2.21.0 | Cannot install | Cannot install | Wheels exist only for cp310 to cp313 |
Installing numpy, pandas, scipy, scikit-learn, matplotlib, pyarrow, and polars took 71 seconds on 3.15, download included, with no compilation. The same set on 3.15t took 6 minutes 51 seconds, almost all of it spent compiling Polars.
When a package has no wheel, uv tells you exactly why. This is what PyTorch looks like on 3.15:
hint: You require CPython 3.15 (`cp315`), but we only found wheels for `torch`
(v2.14.1) with the following Python ABI tags: `cp310`, `cp311`, `cp312`, `cp313`, `cp314`, `cp314t`Check your own project in one minute
You do not need to install anything to find out. Run a dry run against a 3.15 environment:
uv venv --python 3.15 .venv315
uv pip install --dry-run --python .venv315/bin/python -r requirements.txtIf it resolves, you are fine. If you see the cp315 hint above, that package is your blocker.
A source build is not a failure, but it is slow, and it needs a compiler (Rust for Polars, C++ for DuckDB). In CI, a source build can turn a one-minute job into a ten-minute job. Pin 3.14 in CI until wheels land.
Step 3: Use lazy imports to make scripts start faster
I think lazy imports are the best reason to move to 3.15. The problem they fix is familiar.
A command-line script usually imports everything at the top. Even my_tool.py --help pays the full cost of loading pandas, matplotlib, and scikit-learn, although it never uses them.
The before and after (tested)
The eager version:
import sys
import pandas as pd
import matplotlib.pyplot as plt
import sklearn.linear_model
if len(sys.argv) > 1 and sys.argv[1] == "--help":
print("usage: tool [--help] file.csv")
sys.exit(0)The lazy version only adds the lazy keyword:
import sys
lazy import pandas as pd
lazy import matplotlib.pyplot as plt
lazy from sklearn import linear_model
if len(sys.argv) > 1 and sys.argv[1] == "--help":
print("usage: tool [--help] file.csv")
sys.exit(0)
df = pd.DataFrame({"x": [1, 2, 3], "y": [2, 4, 6]})
model = linear_model.LinearRegression().fit(df[["x"]], df["y"])
print(model.coef_)Results on our machine, five runs each of python script.py --help:
| Version | Time | Lines in -X importtime |
|---|---|---|
| Eager imports | 2.69 to 2.74 s | 1,747 |
lazy import | 0.01 s | 27 |
The full code path still works: the lazy version fit the regression and printed 2.0. pandas loads at the line that first uses pd.
Your absolute numbers will differ. In this environment, import sklearn.linear_model alone took 2.2 s and import pandas took 0.86 s. The pattern holds for any branch of your program that does not touch a heavy library.
See what lazy imports are doing
A lazy name is a proxy until you use it. You can see it:
import sys
lazy import json
print(type(globals()["json"])) # <class 'lazy_import'>
print("json" in sys.modules) # False
json.dumps(1)
print(type(globals()["json"]), "json" in sys.modules) # <class 'module'> TrueTo find which imports are slow in the first place, run python -X importtime your_script.py 2> imports.log and sort by the second column.
Three places where lazy is a SyntaxError (tested)
lazy only works at module level. We hit all three of these errors:
| Where | Error message |
|---|---|
| Inside a function | SyntaxError: lazy import not allowed inside functions |
Inside try / except | SyntaxError: lazy import not allowed inside try/except blocks |
| Star import | SyntaxError: lazy from ... import * is not allowed |
The try/except rule matters most. Code like try: import ujson as json except ImportError: import json cannot become lazy. Keep the fallback eager, or move it into a small module that you import lazily.
The official docs add that class bodies and from __future__ imports cannot be lazy either.
Errors move to first use
A missing module no longer fails at the top of the file:
lazy import not_a_real_module
print("defined ok") # this prints
not_a_real_module.x # ModuleNotFoundError is raised hereThat is convenient for optional dependencies. It also means a typo in an import can hide until a rarely used code path runs. Keep at least one test that exercises each lazy import.
Making everything lazy without editing code
You can switch the whole program to lazy imports from the command line:
python -X lazy_imports=all my_tool.py --help
# or
PYTHON_LAZY_IMPORTS=all python my_tool.py --helpOur unmodified eager script dropped to 0.01 s this way. Be careful with modules that do work at import time, though. We made a tiny plugin module that prints and registers itself when imported. With lazy_imports=all, the registration never ran, because the program never touched the module's name:
normal: plugin registered / main done
all: main doneLibraries that register plugins, codecs, or matplotlib backends on import can break the same way. Use =all to measure the potential gain, then add lazy by hand to the imports you know are safe. For finer control, the docs describe sys.set_lazy_imports_filter().
Supporting 3.14 and 3.15 in the same file
The lazy keyword is a syntax error on 3.14 and older. If your code must run on both, use the module-level __lazy_modules__ list instead. Older versions ignore it, and 3.15 treats those imports as lazy:
__lazy_modules__ = ["pandas"]
import sys
import pandas as pd # lazy on 3.15, eager on 3.14
print("pandas" in sys.modules) # False on 3.15 until pd is usedWe confirmed that pandas stayed out of sys.modules on 3.15 with this pattern.
Step 4: Handle the UTF-8 default
On 3.15, open() without an encoding argument uses UTF-8 on every platform. We confirmed sys.flags.utf8_mode == 1 by default and 0 with PYTHONUTF8=0.
On macOS and Linux this changes almost nothing. On Windows it changes behavior for any code that reads legacy files without naming an encoding: Excel CSV exports in cp1252, Japanese Shift-JIS (cp932) files, or Chinese GBK files. Those reads can now raise UnicodeDecodeError or produce garbled text.
The fix is to be explicit:
import pandas as pd
df = pd.read_csv("export.csv", encoding="cp932") # Japanese Excel export
with open("legacy.txt", encoding="locale") as f: # old behavior: system locale
text = f.read()To find affected code before upgrading, run your tests on your current Python with -X warn_default_encoding. To get the old behavior back temporarily, set PYTHONUTF8=0.
Step 5: Decide on the free-threaded build (3.15t)
Free-threaded Python removes the GIL, so pure-Python threads can use several CPU cores. We ran a CPU-bound loop four times, sequentially and then on four threads:
| Build | Sequential | 4 threads | Speedup |
|---|---|---|---|
| Python 3.15 | 0.72 s | 0.71 s | 1.0× |
| Python 3.15t | 0.65 s | 0.19 s | 3.4× |
The GIL stayed off after importing numpy, pandas, scikit-learn, polars, matplotlib, and pyarrow (sys._is_gil_enabled() returned False).
Use 3.15t if you run CPU-heavy pure-Python code in threads and your dependencies have cp315t wheels. Skip it if you depend on Polars, DuckDB, or deep-learning libraries. Skip it as well if your heavy work already runs inside NumPy or pandas, which release the GIL on their own.
Should you upgrade now?
| Your situation | Recommendation |
|---|---|
| Scripts, CLIs, automation with the numpy / pandas / sklearn stack | Upgrade. Lazy imports are a real win |
| Jupyter notebooks for analysis | Upgrade in a new environment; keep your old kernel for a few weeks |
| PyTorch or TensorFlow projects | Stay on 3.14 (uv python pin 3.14) |
| DuckDB or Polars-on-3.15t in CI | Wait for wheels, or accept compile time and a compiler in CI |
| Windows code that reads legacy-encoded files | Add explicit encoding= first, then upgrade |
Using 3.15 in Jupyter
ipykernel installs and works on 3.15. To register a 3.15 kernel from a uv project:
uv add ipykernel
uv run python -m ipykernel install --user --name py315 --display-name "Python 3.15"If you work in notebooks, RunCell (opens in a new tab) runs as an AI agent inside Jupyter, so it can see your live kernel state. That helps when you test whether an existing notebook still runs on the new kernel. PyGWalker (opens in a new tab) also works on 3.15 (tested: pyg.to_html() rendered a DataFrame), with the DuckDB compile noted above.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
uv python install 3.15 installs 3.15.0a4 | uv is older than 0.13 | uv self update, or upgrade through Homebrew / pipx |
No solution found … no wheels with a matching Python ABI tag (cp315) | The package has no 3.15 wheel yet (torch, tensorflow) | uv python pin 3.14 for that project |
| Install hangs for minutes on duckdb or polars-runtime | Source build because no matching wheel exists | Wait it out (needs a C++ or Rust compiler), or use 3.14 / regular 3.15 instead of 3.15t |
SyntaxError: lazy import not allowed … | lazy inside a function, try, or a class | Move the import to module level, or keep it eager |
| A plugin or backend stops registering | -X lazy_imports=all skipped an import with side effects | Remove =all; use lazy only on chosen imports |
UnicodeDecodeError on Windows after upgrading | UTF-8 is now the default encoding | Pass encoding="cp1252" / "cp932" / "gbk" or encoding="locale" |
| New uv project unexpectedly on 3.15 | uv 0.13 changed the default | uv python pin 3.14 |
SyntaxWarning: 'return' in a 'finally' block from a dependency | The library has old code flagged by newer Python | Harmless; it comes from the library, not your code |
FAQ
When was Python 3.15 released? Python 3.15.0 was released on October 9, 2026.
How do I install Python 3.15 with uv? Update uv to 0.13.0 or newer, then run uv python install 3.15. Older uv versions only know the 3.15 alpha builds.
Does pandas work on Python 3.15? Yes. pandas 3.0.6 ships cp315 and cp315t wheels, as do numpy, scipy, scikit-learn, matplotlib, and pyarrow.
Does PyTorch support Python 3.15? Not yet. As of October 11, 2026, torch 2.14.1 has wheels only up to cp314 and cp314t, so it cannot be installed on 3.15.
What are lazy imports in Python 3.15? Writing lazy import module makes Python create a lightweight proxy and load the module only when the name is first used. This cuts startup time for code paths that never touch the module.
Can I use lazy imports and still support Python 3.14? Yes. Define __lazy_modules__ = ["module_name"] at the top of the module. Python 3.15 treats those imports as lazy, and older versions ignore the list.
How do I keep a project on Python 3.14? Run uv python pin 3.14 in the project folder. It writes a .python-version file that uv respects.
Related Guides
- How to Upgrade Python on Windows, Mac, Linux, and Virtual Environments
- Python Version Managers Compared
- How to Check Your Python Version
- How to Upgrade Python Packages
- Polars vs pandas
- Python Circular Import Errors and Fixes