Python — Reference
Source: https://docs.python.org/3/
Python
- Created: 1991 by Guido van Rossum (BDFL emeritus); now stewarded by the Python Software Foundation (PSF) and a Steering Council
- Latest stable: 3.14.x (2025-10-07); 3.15 in development as of 2026-05
- Paradigms: multi-paradigm — imperative, object-oriented, functional, procedural; gradual typing via
typing - Typing: dynamic (strong); optional gradual static via type hints + external checkers (mypy, pyright, pyrefly, ty)
- Memory: garbage collected — reference counting + cyclic garbage collector; CPython’s GC is generational (revert from incremental GC was applied in 3.14.5)
- Compilation: interpreted via bytecode on a stack-based VM; experimental copy-and-patch JIT (PEP 744) in 3.13+; optional tail-call interpreter in 3.14
- Primary domains: scripting, data science / ML, scientific computing, web backends, automation / DevOps, education, glue code
- Official docs: https://docs.python.org/3/
At a glance
- Reference implementation: CPython. Alternative implementations: PyPy (tracing JIT), GraalPy, MicroPython (embedded), Jython (legacy JVM), IronPython (.NET).
- GIL: historically one global lock per interpreter. PEP 703 free-threaded build is officially supported as of 3.14 (PEP 779). Subinterpreters with per-interpreter GIL via
concurrent.interpreters(PEP 734). - Versioning: annual cadence — minor X.Y in October, 2 years bugfix + 3 years security (5-year support window per PEP 602).
- Governance: PEP (Python Enhancement Proposal) process; Steering Council elected annually since PEP 13 (2019).
Getting started
Install (official):
- Windows / macOS: installer from https://www.python.org/downloads/
- macOS recommended: Homebrew
brew install python@3.14 - Linux: distro packages, or
apt install python3.14on recent distros - Version manager (de facto standard):
uv(Astral) —uv python install 3.14— replacespyenvfor most workflows.pyenvstill common.
Hello world:
print("Hello, world!")Project layout (modern, PEP 621):
myproject/
pyproject.toml
src/
mypkg/
__init__.py
core.py
tests/
test_core.py
README.md
Package manager / build tool:
uv(Astral, Rust): single tool — env mgmt, lockfile, install, run. Effectively the default in 2025-26.pip+venv: built-in baseline.poetry,hatch,pdm: alternative project managers.- Build backends:
hatchling,setuptools,flit-core,pdm-backend,maturin(for Rust extensions).
REPL: python — 3.14 ships a new pyrepl-based REPL with multiline editing, syntax highlighting, and tab-completion of imports. ipython is the power-user REPL.
Basics
Primitives: int (arbitrary precision), float (IEEE 754 double), complex, bool, bytes, str (Unicode), None. No fixed-width integer types in pure Python.
Variables / scope: dynamically typed; LEGB scope (Local, Enclosing, Global, Builtin). global and nonlocal keywords. No block scope — if/for do not introduce scope.
x: int = 10 # annotation is non-binding at runtime
y, z = 1, 2 # tuple unpacking
a, *rest = [1, 2, 3] # PEP 3132 starred unpackingControl flow: if/elif/else, for ... in, while, match/case (PEP 634, structural pattern matching, 3.10+), try/except/else/finally. 3.14 allows bracketless multi-exception: except TimeoutError, ConnectionRefusedError: (PEP 758).
Functions: first-class; lexical closures; positional-only (/) and keyword-only (*) markers; defaults; *args/**kwargs; lambdas (single expression).
def fetch(url: str, /, *, timeout: float = 5.0, **headers: str) -> bytes: ...Strings:
-
f-strings (PEP 498, expanded in 3.12 PEP 701):
f"{x = :>5.2f}" -
t-strings (PEP 750, 3.14) — return
Templateobjects for safe SQL/HTML/shell interpolation:query = t"SELECT * FROM users WHERE id = {user_id}" -
raw
r"...", byteb"...", unicode escape\N{GREEK SMALL LETTER ALPHA}.
Built-in collections: list (mutable seq), tuple (immutable seq), dict (insertion-ordered hash map since 3.7), set, frozenset. Comprehensions: list / dict / set / generator.
squares = {n: n*n for n in range(10) if n % 2}Intermediate
Type system (PEP 484+): structural via typing.Protocol, generics, TypeVar, ParamSpec, TypeVarTuple, Concatenate, Self, Never, LiteralString, Annotated. PEP 695 (3.12) new generic syntax:
def first[T](xs: list[T]) -> T: ...
type Vector[T] = list[T]PEP 649/749 (3.14): annotations are lazily evaluated via “annotate functions”; introspect with the new annotationlib module. Checkers: mypy, pyright (Microsoft), ty (Astral, Rust), pyrefly (Meta, Rust).
Modules / packages: every directory with __init__.py (or namespace packages, PEP 420) is a package. Imports follow sys.path. Distribution = “wheels” (.whl) on PyPI; metadata in pyproject.toml (PEP 621).
Errors: exceptions are the only mechanism. BaseException / Exception hierarchy. Exception groups (ExceptionGroup, PEP 654) and except* syntax for parallel/concurrent failure handling. raise ... from ... for chaining.
Concurrency primitives:
threading— bound by GIL except in free-threaded builds (3.13+ experimental, 3.14 supported).multiprocessing— process per worker, IPC via pickle.asyncio— single-thread cooperative;async def/await,TaskGroup(3.11),asyncio.Runner, structured concurrency patterns.concurrent.futures—ThreadPoolExecutor/ProcessPoolExecutor.concurrent.interpreters(3.14, PEP 734) — true multicore via subinterpreters with per-interpreter GIL.
File I/O / networking: open() (text/binary, encoding-aware), pathlib.Path (preferred over os.path), socket, http.client, urllib, ssl. High level: httpx, requests, aiohttp.
Stdlib highlights: itertools, functools, dataclasses (@dataclass(slots=True, kw_only=True)), enum, typing, pathlib, subprocess, json, sqlite3, re, collections (Counter, defaultdict, deque), contextlib, compression.zstd (3.14, PEP 784), tomllib (3.11+, read-only), zoneinfo (3.9+, IANA tz), graphlib.TopologicalSorter, statistics, secrets (cryptographically strong randomness), tomllib (read TOML, write via tomli-w 3rd party).
Async patterns in 3.11+:
import asyncio
async def fetch_all(urls):
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch(u)) for u in urls]
# All tasks complete OR all cancel on first exception; errors aggregate into ExceptionGroup
return [t.result() for t in tasks]
async def with_timeout():
async with asyncio.timeout(5.0): # cleaner than wait_for; nestable
await long_op()
# Structured concurrency with cancellation propagation
async def race(coro1, coro2):
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(coro1)
t2 = tg.create_task(coro2)
# First to finish causes the other to cancelAdvanced
Memory model: CPython refcount + cyclic GC. Tune via gc module — gc.set_threshold(), gc.freeze() to skip pre-fork objects, gc.disable() for batch jobs. sys.getrefcount, tracemalloc for leak hunting. __slots__ to drop the per-instance __dict__ — saves ~40-60% memory per instance, gives 20-30% attribute-access speedup. PEP 683 (3.12+) introduces immortal objects — singletons (None, True, False, small ints, interned strings) skip refcount updates entirely, eliminating cache-line contention under free-threading.
Object layout reality: every CPython object pays a ~28-byte header (PyObject_HEAD: refcount + type pointer + GC list pointers). sys.getsizeof(0) is 28, sys.getsizeof("") is 49, sys.getsizeof([]) is 56. Use __slots__, array.array, numpy, or struct when you need millions of small objects.
Concurrency / parallelism deep dive:
- Free-threaded build (
python3.14t): no GIL, ~5-10% single-thread overhead in 3.14 (down from ~40% in early 3.13). Watch for C extensions that aren’t free-thread-safe — usePy_GIL_DISABLEDchecks. Wheel tagcp314t-cp314tfor free-threaded distributables. NumPy 2.1+, Cython 3.1+, PyTorch 2.5+, scikit-learn 1.5+ already ship free-threaded wheels; ecosystem catching up through 2026. - Subinterpreters (PEP 734,
concurrent.interpreters): each has its own GIL, isolated module state, communication via shared queues /Channelobjects. Lighter-weight than processes (~5 MB vs 30+ MB) but heavier than threads. Good for CPU-bound, embarrassingly-parallel workloads when free-threading isn’t available. asyncioschedulers: default selector loop on Windows/Linux,uvloop(libuv) for 2-4x throughput on POSIX.winloop(libuv port) covers Windows.asyncio.TaskGroup(3.11+) is the structured-concurrency primitive — all tasks cancel together on failure, errors aggregate intoExceptionGroup.asyncio.timeout()context manager (3.11+) supersedeswait_for.anyio+trioare stricter structured-concurrency alternatives that wrap both backends.- JIT (PEP 744,
--enable-experimental-jitin 3.13+): copy-and-patch JIT — pre-compiled stencils stitched together at runtime. ~5% wins on benchmarks today; 3.14 added tier-2 optimizer + symbolic execution; 3.15 targets 10-15% wins. - Tail-call interpreter (3.14, Clang 19+, GCC 15+): rewrites the eval loop as ~200 musttail-call functions, gives the C compiler better register allocation. 3-5% gain on average, up to 10% on dispatch-heavy code.
FFI / interop:
ctypes— call C libs from pure Python.cffi— friendlier C bindings.- C extension API —
Python.h,PyObject*, ref counting; HPy is the portable replacement push. - Cython — Python-superset compiled to C.
- PyO3 (Rust) + maturin for shipping wheels — extremely common for new native code.
- Free-threaded wheel tag:
cp314t.
Reflection: inspect, dis (bytecode), ast (parse tree), sys.settrace / sys.setprofile, sys._getframe, __class__, __dict__, __mro__.
Performance tuning:
- Profilers:
cProfile(deterministic, instrumenting, ~20% overhead),pyinstrument(sampling, low overhead, call-tree view),py-spy(sampling, no-instrument, attaches to running PID, Rust-based),scalene(CPU + GPU + memory, AI-aware),austin(frame-stack sampler, perf-compatible output),memray(Bloomberg, allocation tracker, generates flame graphs). perfintegration on Linux (3.12+:PYTHONPERFSUPPORT=1) — Python frames show up in Linux perf flamegraphs natively.- JIT: enable experimental copy-and-patch JIT with
PYTHON_JIT=1on JIT-enabled builds. - Tail-call interpreter (3.14, Clang 19+): 3-5% benchmark gain.
- Specialized adaptive interpreter (PEP 659): hot bytecodes get rewritten to specialized variants (e.g.,
BINARY_OP→BINARY_OP_ADD_INT). Hidden, fully automatic; opcode-level inline caches.
Modern data stack (2024-2026):
| Tool | Role | Why now |
|---|---|---|
| NumPy 2.0+ | n-dim arrays, BLAS/LAPACK | Cleaned-up API (June 2024), 30% smaller binary, free-threaded support |
| Polars | dataframes (Rust + Arrow) | 5-30x faster than pandas; lazy + streaming; Polars 1.0 May 2024 |
| DuckDB | embedded OLAP SQL | Sub-second analytics over Parquet/CSV; duckdb.sql("SELECT ...") directly on DataFrames |
| PyArrow | zero-copy interchange | Foundation for pandas 2.x, Polars, DuckDB |
| JAX | autodiff + XLA + GPU/TPU | Functional, traceable, used by DeepMind / Anthropic |
| PyTorch 2.5+ | DL with torch.compile | Inductor → Triton kernels; 30-100% speedups on training |
| Pandas 2.x | tabular workhorse | PyArrow backend (dtype_backend="pyarrow") for 2-5x speedups |
| xarray | labeled n-d arrays | Climate, geo, NetCDF/Zarr |
| modin / dask | distributed pandas | When data outgrows one machine |
Native-code paths: Cython (Python-superset compiled to C) remains the mainstream choice for sci/ML extensions. PyO3 + maturin (Rust → wheel) is the modern alternative; production-shipping at Cloudflare, Pydantic, Polars, ruff. Mojo (Modular) is Python-superset-ish AOT; still pre-1.0 in 2026. mypyc (used by ruff, black, mypy itself) AOT-compiles type-annotated Python to C extensions — 4x typical speedup with zero source changes.
God mode
Metaclasses: class Foo(Bound, metaclass=Meta). Metaclass __new__/__init__/__call__ runs at class creation. Use sparingly — __init_subclass__ covers 90% of cases.
class PluginBase:
registry = {}
def __init_subclass__(cls, *, key, **kw):
super().__init_subclass__(**kw)
cls.registry[key] = cls
class JSONPlugin(PluginBase, key="json"): ...Descriptors: the protocol behind property, classmethod, staticmethod, __slots__, ORM fields. Implement __get__/__set__/__delete__ and __set_name__.
Generic class subscript: __class_getitem__ lets Foo[int] work without metaclass tricks. typing.Generic uses it.
AST manipulation: ast.parse(src) → mutate → ast.unparse() or compile(). Use ast.NodeTransformer. Powers tools like mypy, black, ruff lints.
Bytecode / disassembly: dis.dis(fn) shows opcodes. compile(src, '<str>', 'exec') returns a code object; build them by hand with code.replace(...). The “specialized adaptive interpreter” (PEP 659) rewrites opcodes at runtime.
sys.settrace / sys.setprofile: the basis of pdb, coverage.py, sampling profilers. Per-frame Python-callable hooks.
Remote / out-of-process: sys.remote_exec(pid, "script.py") (3.14, PEP 768) — zero-overhead attach to a running interpreter. Backs python -m pdb -p PID.
C extension API: PyMethodDef, PyTypeObject, tp_traverse for GC. HPy abstracts ref counting and works on PyPy/GraalPy. The Limited API (Py_LIMITED_API) lets a single wheel target many Python versions.
Stable internals to know: Py_BuildValue, PyObject_GetAttr, freelists, the eval loop in Python/ceval.c, the MAKE_CELL/COPY_FREE_VARS opcodes for closures, LOAD_ATTR inline caches.
Free-threaded considerations: PEP 683 (immortal objects), per-object locking, biased reference counting. C extensions opt in via Py_mod_gil = Py_MOD_GIL_NOT_USED.
Typing god-tier: TypeVarTuple + Unpack (PEP 646) for variadic generics — used by NumPy/Jax for shape-typed arrays: Array[Float, Unpack[Shape]]. ParamSpec + Concatenate for typed decorators that preserve signatures. Self (PEP 673) returns the actual subclass type. override (PEP 698, 3.12) — explicit @override decorator catches typos at type-check time. LiteralString (PEP 675) for SQL/shell injection prevention. ReadOnly (PEP 705, 3.13) for read-only TypedDict fields. TypeIs (PEP 742, 3.13) — narrowing in both true and false branches. @dataclass_transform (PEP 681) lets ORMs / pydantic-like libs participate in dataclass-style inference.
Pattern matching (PEP 634, 3.10+):
match event:
case {"type": "user", "id": int(uid)}: # dict pattern + type check
handle_user(uid)
case Point(x=0, y=y): # class pattern
on_y_axis(y)
case [first, *rest] if len(rest) > 5: # sequence + guard
process_batch(first, rest)
case _:
ignore()Patterns are not regex; they’re destructuring + type tests + guards. The __match_args__ class attribute (auto on dataclasses) drives positional matching.
Validation stack: Pydantic v2 (2023+, Rust core pydantic-core) — 5-50x faster than v1; standard for FastAPI, LangChain, Modal, Hugging Face inference. msgspec — fastest validator (Cython + msgpack), zero allocations on happy path. attrs — battle-tested, doesn’t enforce types at runtime by default. dataclasses (stdlib, no validation). Marshmallow (legacy schemas).
Idioms & style
- PEP 8 — formatting;
snake_casefor functions/vars,PascalCasefor classes,SCREAMING_SNAKEfor constants,_protected,__name_mangled. - PEP 20 (Zen of Python):
import this. “Flat is better than nested,” “explicit is better than implicit.” - Formatters:
ruff format(Astral) — Black-compatible, much faster, dominant in 2025-26.blackstill widely used. - Linters:
ruff(Rust, single binary, replaces flake8/pylint/isort/pydocstyle/pyupgrade for most teams).pylintfor deeper checks. - Type checkers:
mypy,pyright,ty(Astral),pyrefly(Meta). - Pythonic patterns:
- “Easier to ask forgiveness than permission” (EAFP) — try/except instead of pre-checking.
- Iterator + comprehension over manual loops.
- Context managers (
with) for resource lifetime. - Dataclasses or
attrsover hand-written__init__. @cached_property,functools.lru_cachefor memoization.- Avoid mutable default args (
def f(x=[])is a classic bug).
- What reviewers flag: mutable defaults, bare
except:,from x import *, manual resource cleanup withoutwith, missing type hints in public APIs, classes used where a function would do.
Ecosystem
| Domain | Tools |
|---|---|
| Web (sync) | Django 5.x, Flask 3.x, Pyramid |
| Web (async) | FastAPI, Starlette, Litestar, Sanic, Robyn (Rust), Quart |
| Data | NumPy 2.x, pandas 2.x, Polars, PyArrow, DuckDB, Modin, Dask |
| ML / DL | PyTorch 2.5+, JAX, TensorFlow, scikit-learn, Hugging Face Transformers, XGBoost, LightGBM |
| LLM apps | LangChain, LlamaIndex, DSPy, instructor, Pydantic AI, Marvin, Outlines, Guidance, Mirascope |
| Inference | vLLM, SGLang, llama.cpp (via llama-cpp-python), TGI, MLX, Ollama bindings |
| Scientific | SciPy, SymPy, Astropy, Biopython, Numba, CuPy, scikit-image |
| Async I/O | httpx, aiohttp, anyio, trio, aiofiles |
| ORM / DB | SQLAlchemy 2.x, Django ORM, SQLModel, asyncpg, psycopg3, Tortoise, Piccolo |
| Validation | Pydantic v2, attrs, msgspec, marshmallow |
| Package mgmt | uv (Astral), Poetry, Hatch, PDM, pip+venv, Rye (merged into uv), Pixi (conda-native) |
| Testing | pytest (de facto), unittest (stdlib), hypothesis (property-based), tox/nox (matrix runners), pytest-xdist |
| Docs | Sphinx, MkDocs (+ Material), pdoc, mkdocstrings |
| Build / native | maturin (Rust), Cython, mypyc, scikit-build-core, meson-python, setuptools, hatchling, flit |
| Notebooks | Jupyter, JupyterLab, marimo (reactive, git-friendly), Hex, Google Colab, Deepnote |
| GUI / TUI | Qt (PyQt6/PySide6), Tkinter (stdlib), Kivy, Textual (TUI, Rich-based), Toga, Flet |
| Web frontend | NiceGUI, Reflex, Streamlit, Gradio, Solara, Panel, Dash |
| Observability | OpenTelemetry SDK, structlog, loguru, Sentry SDK, Prometheus client |
| Notable users | Google, Instagram (Django at scale), Dropbox (built it, uses mypy), Netflix, Spotify, NASA, OpenAI, Anthropic, Stripe, JPMorgan, CERN |
Modern code examples
Async with TaskGroup + timeout (3.11+)
import asyncio
import httpx
async def fetch_one(client: httpx.AsyncClient, url: str) -> dict:
r = await client.get(url, timeout=10)
r.raise_for_status()
return r.json()
async def main():
urls = ["https://api.example.com/a", "https://api.example.com/b"]
async with httpx.AsyncClient() as client:
async with asyncio.timeout(30):
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch_one(client, u)) for u in urls]
results = [t.result() for t in tasks]
return results
asyncio.run(main())Pydantic v2 with FastAPI
from pydantic import BaseModel, EmailStr, Field, field_validator
from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException
class UserCreate(BaseModel):
email: EmailStr
name: Annotated[str, Field(min_length=1, max_length=100)]
age: Annotated[int, Field(ge=0, le=150)]
@field_validator("name")
@classmethod
def title_case(cls, v: str) -> str:
return v.title()
app = FastAPI()
@app.post("/users", response_model=UserCreate)
async def create_user(user: UserCreate) -> UserCreate:
return userPolars dataframe pipeline
import polars as pl
df = (
pl.scan_csv("data.csv") # lazy
.filter(pl.col("revenue") > 1000)
.with_columns([
(pl.col("revenue") - pl.col("cost")).alias("profit"),
pl.col("date").str.to_date("%Y-%m-%d"),
])
.group_by("region")
.agg([
pl.col("profit").sum(),
pl.col("profit").mean().alias("avg_profit"),
pl.len().alias("rows"),
])
.sort("profit", descending=True)
.collect(streaming=True) # only here does work happen
)Structural pattern matching
def process(event: dict):
match event:
case {"type": "click", "x": int(x), "y": int(y)} if x >= 0 and y >= 0:
click(x, y)
case {"type": "key", "key": str(k), "modifiers": [*mods]}:
keypress(k, mods)
case {"type": "error", **rest}:
log_error(rest)
case _:
raise ValueError(f"unknown event: {event!r}")Gotchas
- Mutable default arguments — evaluated once at def time.
def f(x=[]):shares the list across calls. - Late binding closures in loops —
[lambda: i for i in range(3)]all return 2. Uselambda i=i: i. isvs==—ischecks identity; only safe for singletons (None,True, small ints, interned strs).- Integer / string interning —
a is bfor ints in[-5, 256]may beTrueby accident. - Tuple of one:
(1)is an int,(1,)is a tuple. boolis a subclass ofint—True == 1,isinstance(True, int)is True.- GIL myths — IO-bound threading still helps; CPU-bound usually wants multiprocessing or free-threaded.
- Circular imports — split modules or import inside functions.
__init__.pyshadowing — a package and a sibling module of the same name will conflict.- Pickle is unsafe — never unpickle untrusted data; arbitrary code execution.
subprocess.shell=True— shell injection vector; pass a list argv instead.for x in dict:mutating —RuntimeError: dictionary changed size during iteration.- Newcomers from Java/C#: no
private; convention is leading_. No method overloading; usefunctools.singledispatchor default args. - Newcomers from JS: no implicit type coercion (
"1" + 1raises). Truthiness is per-type —bool([])is False butbool([0])is True.
Tooling renaissance (Astral effect)
The Rust-rewrite-of-Python-tooling trend (Astral, Meta, Microsoft) has reshaped the ecosystem 2023-2026:
| Replaces | Modern tool | Speedup | Built in |
|---|---|---|---|
| pip + venv + pip-tools + pyenv + pipx | uv | 10-100x | Rust |
| black + flake8 + isort + pyupgrade + pydocstyle + autoflake | ruff | 100-1000x | Rust |
| mypy / pyre | ty (Astral), pyrefly (Meta), pyright (MS) | 5-100x | Rust / Rust / TS |
| jupyter (browser cell editor) | marimo | reactive | Python |
| poetry build | maturin (for Rust ext) | native | Rust |
uv specifics: universal lock file uv.lock, single resolver across requirements/dev/build groups, native PEP 723 inline script metadata (uv run script.py reads # /// script header for deps), uv tool run X for ephemeral execution like pipx, uv python install 3.14 for managed interpreters. Effectively replaces 5+ tools for most projects.
ruff capability matrix:
- 800+ rules covering pyflakes, pycodestyle, mccabe, pylint, isort, pydocstyle, flake8-bugbear, flake8-comprehensions, flake8-simplify, pep8-naming, pyupgrade, eradicate, perflint.
ruff formatis Black-compatible (>99.9% match) but 30x faster.- Native
--fixand--unsafe-fixesapply auto-fixes; SARIF output for GitHub Code Scanning. [tool.ruff.lint.per-file-ignores]per-file rule customization.- Plugin-free architecture — ships everything in one Rust binary.
Modern packaging and distribution
Wheel publishing in 2026:
- Pure-Python wheels (
py3-none-any) — simplest, install anywhere. - Platform wheels (e.g.,
cp314-cp314-manylinux_2_28_x86_64) for native code. manylinux_2_28is the current target (CentOS 8 / RHEL 8 ABI baseline);manylinux_2_34proposed.musllinuxfor Alpine/Distroless containers.- macOS wheels:
macosx_11_0_arm64(M-series),macosx_10_15_x86_64, often auniversal2superset. cibuildwheel(PyPA) — CI matrix that builds wheels for every supported triple; standard for any package shipping native code (NumPy, Pillow, Pydantic, ruff).maturinfor Rust extensions — builds + publishes wheels with PyO3 bindings in one step. Used by Polars, Pydantic-core, ruff, Cryptography (partially).scikit-build-corefor CMake-based native builds.- PEP 711 (PyBI) would bundle interpreters into wheels; experimental.
CPython release cadence and support
| Version | Released | Bug-fix until | Security until | Notable |
|---|---|---|---|---|
| 3.10 | Oct 2021 | Apr 2024 | Oct 2026 | match/case, paren-context-managers |
| 3.11 | Oct 2022 | Apr 2025 | Oct 2027 | TaskGroup, ExceptionGroup, 10-60% speedup (Faster CPython) |
| 3.12 | Oct 2023 | Apr 2026 | Oct 2028 | PEP 695 generics, per-interpreter GIL groundwork, f-string formal grammar |
| 3.13 | Oct 2024 | Apr 2027 | Oct 2029 | Free-threaded experimental, JIT experimental, new REPL |
| 3.14 | Oct 2025 | Apr 2028 | Oct 2030 | t-strings, free-threaded supported, tail-call interpreter, subinterpreters API |
| 3.15 | Oct 2026 (planned) | — | — | Continued JIT tier-2 work, more typing improvements |
Annual release in October; bugfix for 2 years; security for 5 years total. PEP 602.
PEP 723 inline metadata (3.11+, popularized by uv) — make a script self-contained:
# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx", "rich"]
# ///
import httpx, richRun with uv run script.py — uv builds a temp venv, installs the deps, executes.
Free-threaded Python (PEP 703 + 779) in depth
The GIL has been Python’s biggest limitation since 1991. The free-threaded build (officially supported in 3.14 per PEP 779; experimental in 3.13) finally removes it.
How it works under the hood:
- PEP 683 immortal objects — singletons (
None,True,False, small ints, interned strings) skip refcount updates entirely; no cache contention. - Biased reference counting — each object has a thread-local “owning thread”; common case (single thread accessing) is a non-atomic local increment. Atomic operations only on cross-thread access.
- Per-object locking (
PyMutex) — fine-grained locks ondict,list, and other mutable containers replace global serialization. - Mimalloc is bundled as the default allocator for free-threaded builds — better multi-thread scaling than glibc malloc.
- Specialized adaptive interpreter (PEP 659) safely shared across threads via versioning.
What works today (3.14):
- Pure Python code: GIL-free, scales close to linearly on CPU-bound workloads.
- Stdlib: thread-safe.
- C extensions: must opt in with
Py_mod_gil = Py_MOD_GIL_NOT_USEDand audit for races; mainstream packages shipping free-threaded wheels in 2025-26 include NumPy 2.1+, Cython 3.1+, PyTorch 2.5+, scikit-learn 1.5+, Pillow 11+, lxml.
Performance trade-off: single-thread overhead dropped from ~40% in 3.13 to ~5-10% in 3.14; expected to be ~5% by 3.16. Most workloads benefit; CPU-bound multi-threaded Python (long requested) now possible without multiprocessing.
Modern observability stack
| Layer | Recommended |
|---|---|
| Logging | structlog or loguru (configurable, JSON output) |
| Metrics | prometheus-client, opentelemetry-instrumentation |
| Tracing | OpenTelemetry SDK + auto-instrumentation for Flask/FastAPI/SQLAlchemy/httpx/asyncpg/Celery |
| Error reporting | Sentry SDK (auto-captures unhandled exceptions, performance traces) |
| Profiling | Continuous: pyroscope agent or py-spy --pyspy-http; ad-hoc: scalene, memray |
| Debugging | debugpy (VS Code/PyCharm), pdb/ipdb, pdbpp (better TUI), pudb (curses) |
Modern Python project stack (2026)
The reference “new project” toolchain in late-2025 / 2026 has consolidated around Astral’s tooling + a few stable PEPs:
| Concern | Pick | Why |
|---|---|---|
| Interpreter mgmt | uv python install 3.14 | Replaces pyenv; cross-platform, no shell rc-file dance |
| Env + lock + install | uv (uv lock, uv sync, uv add, uv run) | One binary, ~10-100x faster than pip+pip-tools+venv |
| Format + lint | ruff format + ruff check --fix | Single binary covers Black + isort + flake8 + pyupgrade + pydocstyle |
| Type-check | pyright (deep) or ty (Astral, fast) | mypy still common but losing ground on perf |
| Test | pytest + pytest-xdist (parallel) + hypothesis (property) | nox / tox for matrix orchestration |
| Native wheels | maturin (Rust ext) or cibuildwheel (C/C++ matrix) | PyO3 + maturin is the new default for native code |
| Deploy targets | Modal, Replicate, Fly.io, Render, AWS Lambda Powertools, Google Cloud Run | All ship first-class Python images / uv support |
PEP cheatsheet worth knowing for pyproject.toml:
- PEP 621 —
[project]metadata (name, version, dependencies, optional-dependencies). - PEP 631 — explicit
dependencies = [...]array (replacessetup.py install_requires). - PEP 723 — inline script metadata via
# /// scriptheader;uv run script.pyresolves it on the fly. - PEP 735 —
[dependency-groups]for dev/docs/test groups (uv + pip 25+ both support). - PEP 751 —
pylock.tomluniversal lock format (proposed; uv ships its ownuv.lockuntil standardization).
Worked gotcha examples
1. Mutable default argument — the default is evaluated once at def time:
def append_to(item, target=[]):
target.append(item)
return target
append_to(1) # [1]
append_to(2) # [1, 2] ← the same list!
# Fix: use None sentinel
def append_to(item, target=None):
if target is None:
target = []
target.append(item)
return target2. Late-binding closures in a loop — closures capture the variable, not its value:
fns = [lambda: i for i in range(3)]
[f() for f in fns] # [2, 2, 2] — all return final i
# Fix: bind at definition time via default arg
fns = [lambda i=i: i for i in range(3)]
[f() for f in fns] # [0, 1, 2]3. GIL contention on CPU-bound work — threads compete for one bytecode-executing core (on non-free-threaded builds):
# Bad: 4 threads, near-zero speedup on CPU work
from threading import Thread
ts = [Thread(target=compute_pi, args=(1_000_000,)) for _ in range(4)]
for t in ts: t.start()
for t in ts: t.join() # ~4x serial time
# Fix 1: ProcessPoolExecutor (forks/spawns, sidesteps GIL)
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=4) as ex:
list(ex.map(compute_pi, [1_000_000]*4))
# Fix 2 (3.14+): free-threaded build python3.14t — pure threads scale linearly
# Fix 3 (3.14+): concurrent.interpreters — subinterpreter per worker, per-interpreter GILCitations
- Official docs: https://docs.python.org/3/
- What’s new in 3.14: https://docs.python.org/3/whatsnew/3.14.html
- PEP index: https://peps.python.org/
- PEP 8 (style): https://peps.python.org/pep-0008/
- PEP 703 (free-threaded): https://peps.python.org/pep-0703/
- PEP 779 (free-threaded officially supported): https://peps.python.org/pep-0779/
- PEP 750 (t-strings): https://peps.python.org/pep-0750/
- PEP 734 (subinterpreters): https://peps.python.org/pep-0734/
- PEP 649 + 749 (deferred annotations): https://peps.python.org/pep-0649/
- PEP 695 (type parameter syntax): https://peps.python.org/pep-0695/
- Release schedule: https://devguide.python.org/versions/
- Steering Council / governance (PEP 13): https://peps.python.org/pep-0013/
- uv (Astral): https://docs.astral.sh/uv/
- ruff (Astral): https://docs.astral.sh/ruff/