Why this comparison
If you automate file ops, CLI tools, or scheduled jobs with Python, upgrading from 3.10 to 3.13 brings performance boosts, modern concurrency primitives, and simpler config parsing—often with little code change.
TL;DR differences
Feature | Python 3.10 | Python 3.13 |
---|---|---|
Performance | Baseline | Noticeably faster due to interpreter optimizations (3.11+) |
Config parsing | No built-in TOML | Built-in tomllib (no extra dep) |
Async orchestration | No asyncio.TaskGroup | asyncio.TaskGroup simplifies concurrent tasks |
Error handling | No ExceptionGroup/except* | Exception groups for concurrent errors |
F-strings | Older parser | More flexible f-strings (3.12+ improvements included) |
GIL options | Traditional GIL only | Experimental no-GIL/free-threaded build (opt-in) |
Packaging | distutils present (deprecated) | distutils removed (use setuptools) |
Typing | Many features via typing_extensions | More in stdlib: Self, NotRequired, Required, etc. |
Notes:
- 3.13 includes all improvements from 3.11 and 3.12.
- No-GIL is experimental and requires a special build; most users still use the standard GIL build.
Minimal working example (automation-friendly)
This script runs small tasks concurrently. On 3.13 (or any 3.11+), it uses asyncio.TaskGroup; on 3.10, it falls back to asyncio.gather. It also uses tomllib if available to read a simple optional tasks.toml.
# save as run_tasks.py
import sys
import asyncio
from pathlib import Path
try:
import tomllib # Python 3.11+
def load_toml(data: bytes):
return tomllib.loads(data.decode("utf-8"))
except ModuleNotFoundError:
# Python 3.10: fall back if you have tomli installed; otherwise use defaults
try:
import tomli as _tomli
def load_toml(data: bytes):
return _tomli.loads(data.decode("utf-8"))
except ModuleNotFoundError:
def load_toml(data: bytes):
raise RuntimeError("No TOML parser available; install 'tomli' for Python 3.10")
DEFAULT_CONFIG = {
"tasks": [
{"code": "print('task A')"},
{"code": "import time; time.sleep(0.1); print('task B')"},
{"code": "print('task C')"},
]
}
async def run_snippet(code: str):
# Use the running interpreter to execute a snippet; cross-platform
proc = await asyncio.create_subprocess_exec(
sys.executable, "-c", code,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,
)
out, err = await proc.communicate()
if out:
print(out.decode().rstrip())
if err:
print(err.decode().rstrip())
return proc.returncode
def load_config():
cfg_path = Path("tasks.toml")
if not cfg_path.exists():
return DEFAULT_CONFIG
data = cfg_path.read_bytes()
try:
return load_toml(data)
except Exception:
# Fallback to defaults if parsing fails
return DEFAULT_CONFIG
async def main():
cfg = load_config()
codes = [t.get("code", "print('no code')") for t in cfg.get("tasks", [])]
if sys.version_info >= (3, 11):
# Python 3.11+ (includes 3.13): TaskGroup for structured concurrency
from asyncio import TaskGroup
async with TaskGroup() as tg:
for code in codes:
tg.create_task(run_snippet(code))
else:
# Python 3.10 fallback
await asyncio.gather(*(run_snippet(code) for code in codes))
if __name__ == "__main__":
asyncio.run(main())
Optional tasks.toml to try:
[tasks]
# This format expects an array of tables; if you keep DEFAULT_CONFIG it still runs
Run with Python 3.10 and 3.13 to see both paths work.
Quickstart: upgrade plan (3.10 -> 3.13)
- Install Python 3.13
- Keep 3.10 installed for side-by-side testing.
- On Linux/macOS, you’ll often get python3.13; on Windows, use the py launcher.
- Create a new virtual environment
python3.13 -m venv .venv
. .venv/bin/activate # Windows: .venv\\Scripts\\activate
python -m pip install --upgrade pip
- Reinstall your project and tools
pip install -e .
# Or: pip install -r requirements.txt
- Run tests and scripts with extra checks
python -X dev -Wd -m pytest # or your test runner
python -X dev your_script.py
- Fix deprecations and build issues
- Replace distutils usage with setuptools.
- If you parsed TOML via tomli on 3.10, prefer tomllib on 3.13.
- Update C-extension wheels to versions that publish cp313 builds.
- Roll out gradually
- Deploy 3.13 to non-critical jobs first.
- Monitor logs and performance; then promote broadly.
What you gain in 3.13 (practical highlights)
- Faster runtime: the specializing interpreter introduced in 3.11 and refined since speeds up many everyday scripts without code changes.
- Built-in tomllib: parse configuration files (TOML) without third-party dependencies.
- Simpler async orchestration: asyncio.TaskGroup yields clearer, structured concurrency for parallel I/O tasks.
- Better error handling for parallelism: ExceptionGroup and except* help handle multiple failures from concurrent tasks.
- More flexible f-strings: fewer parsing surprises when embedding complex expressions.
- Optional experimental no-GIL build: useful for CPU-bound multithreading in specific scenarios; not the default.
Common pitfalls moving from 3.10
- C-extension compatibility:
- Wheels are versioned by ABI (cp310 vs cp313). Ensure your packages publish cp313 wheels or have build-from-source paths.
- distutils removal:
- If your build or setup scripts import distutils, migrate to setuptools.
- TOML parsing:
- Code that unconditionally imports tomli will fail if it’s not installed. Prefer: try tomllib, fall back to tomli on 3.10.
- Typing changes:
- Some annotations now live in stdlib (e.g., typing.Self). On 3.10, keep typing_extensions for backports.
- Shebangs and launchers:
- Scripts with #!/usr/bin/env python3.10 won’t use 3.13. Prefer python3 or an env-managed shim.
Performance notes
- Expect general speedups from 3.10 -> 3.13 without code changes.
- For I/O-bound automation (HTTP, subprocess, files), use asyncio or thread pools—3.13 won’t auto-parallelize work.
- CPU-bound loops still benefit more from multiprocessing, C-accelerated libraries, or (experimental) no-GIL builds.
- Measure, don’t guess. Quick micro-benchmark template:
import timeit
print(timeit.timeit("sum(range(10000))", number=2000))
Run this under both interpreters in a fresh venv for apples-to-apples.
When to stay on 3.10 briefly
- Critical native deps lack cp313 wheels and compile slowly or fail.
- Your platform doesn’t provide a stable 3.13 build yet.
- You need deterministic environments that are certified only for 3.10.
Otherwise, prefer 3.13 for new automation.
Tiny FAQ
Do I need to rewrite my scripts?
- Usually no. Most 3.10 code runs unchanged. You can incrementally adopt tomllib and TaskGroup.
Can I install 3.13 alongside 3.10?
- Yes. Use python3.13 on Unix-like systems or py -3.13 on Windows.
Will 3.13 make my threads faster?
- Not by default. Only an experimental no-GIL build changes threading behavior. For standard builds, use asyncio or processes.
Is tomllib faster than tomli?
- tomllib is a compiled stdlib module and generally fast; it also removes an external dependency.
What breaks most often?
- distutils-based builds and old C-extension wheels. Update packaging and pin compatible versions.