# Python Recent Features (3.10–3.13)
> Comprehensive guide to recent Python language changes.
---
## Python 3.10 (October 2021)
### 1. Structural Pattern Matching (`match`/`case`)
The biggest syntax addition since decorators.
```python
# Basic pattern matching
def http_error(status):
match status:
case 400:
return "Bad Request"
case 404:
return "Not Found"
case 500:
return "Server Error"
case _:
return "Unknown Status"
# Pattern with guards
def describe_point(point):
match point:
case (0, 0):
return "Origin"
case (0, y):
return f"On Y axis at {y}"
case (x, 0):
return f"On X axis at {x}"
case (x, y) if x == y:
return f"On line y=x at ({x},{y})"
case (x, y):
return f"Point at ({x},{y})"
# Matching sequences
def process_command(cmd):
match cmd:
case ['quit']:
return "Goodbye"
case ['move', direction] if direction in ('up', 'down', 'left', 'right'):
return f"Moving {direction}"
case ['move', *args]:
return f"Moving with args: {args}"
case _:
return "Unknown command"
# Matching classes with capture
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Circle:
center: Point
radius: float
@dataclass
class Rectangle:
top_left: Point
bottom_right: Point
def describe(shape):
match shape:
case Circle(center=Point(x, y), radius=r):
return f"Circle at ({x},{y}) with radius {r}"
case Rectangle(top_left=Point(x1, y1), bottom_right=Point(x2, y2)):
return f"Rectangle from ({x1},{y1}) to ({x2},{y2})"
case _:
return "Unknown shape"
# Named patterns (as)
match event:
case {'type': 'click', 'pos': Point(x, y) as pos}:
print(f"Click at {pos}") # pos = Point(x, y)
# Alternatives
def color_name(c):
match c:
case 'red' | 'green' | 'blue':
return "Primary color"
case 'yellow' | 'cyan' | 'magenta':
return "Secondary color"
# Class patterns with keyword arguments
match shape:
case Circle(radius=0):
print("Zero-radius circle")
case Circle(radius=r) if r > 100:
print("Very large circle")
case Circle():
print("Normal circle")
```
### 2. Improved Type Hints with Union `|` Syntax
```python
# Old (still works):
from typing import Union, Optional
def greet(name: Union[str, None]) -> Union[str, int]:
...
# New (3.10+):
def greet(name: str | None) -> str | int:
...
# Works with built-in collections too
def process(data: dict[str, list[int]]) -> None:
...
# Optional is now redundant:
def maybe(x: int | None) -> None: # same as Optional[int]
...
```
### 3. Parenthesized Context Managers
```python
# Can now split context managers across lines with outer parentheses
from contextlib import ExitStack
with (
open('file1.txt') as f1,
open('file2.txt') as f2,
open('file3.txt') as f3
):
# Process all three files
pass
```
### 4. Better Error Messages
```python
# Missing comma in function call:
# Python 3.9: TypeError: foo() takes 0 positional arguments but 2 were given
# Python 3.10: Points to exact location of missing comma
# Missing closing bracket:
# Python 3.10: Shows the line where the opening bracket was found
```
---
## Python 3.11 (October 2022)
### 1. Exception Groups and `except*`
Handle multiple exceptions at once.
```python
# Raising exception groups
def process_tasks(tasks):
errors = []
for task in tasks:
try:
execute(task)
except Exception as e:
errors.append(e)
if errors:
raise ExceptionGroup("Multiple errors", errors)
# Catching with except*
try:
process_tasks(['a', 'b', 'c'])
except* ValueError as eg:
print(f"Value errors: {eg.exceptions}")
except* TypeError as eg:
print(f"Type errors: {eg.exceptions}")
except* Exception as eg:
print(f"Other errors: {eg.exceptions}")
# Nested exception groups
def main():
sub_errors = []
try:
process_database()
except Exception as e:
sub_errors.append(e)
try:
process_network()
except Exception as e:
sub_errors.append(e)
if sub_errors:
raise ExceptionGroup("sub_errors", sub_errors)
# Custom exception group subclass
class AppErrorGroup(ExceptionGroup):
pass
class ValidationError(Exception): pass
class NetworkError(Exception): pass
try:
raise AppErrorGroup("app errors", [
ValidationError("bad input"),
NetworkError("timeout"),
ValidationError("missing field"),
])
except* ValidationError as eg:
print(f"Validation errors: {len(eg.exceptions)}") # 2
except* NetworkError as eg:
print(f"Network errors: {len(eg.exceptions)}") # 1
```
### 2. Finer-Grained Error Locations
```python
def foo(
a,
b,
c
):
return a + b
# Python 3.11 shows exactly where the issue is:
# TypeError: foo() missing 1 required positional argument: 'c'
# ...
# c ← points to the missing argument
# For syntax errors, shows the caret at the exact problem location
# x = [1, 2, 3
# ^
# SyntaxError: '[' was never closed
```
### 3. Self-Documenting Assertions
```python
# Python 3.10:
# AssertionError
# Python 3.11:
# AssertionError: assert x > 0
# + where x = -5
x = -5
assert x > 0 # AssertionError: assert -5 > 0
```
### 4. `tomllib` — Standard Library TOML Parser
```python
import tomllib
with open('pyproject.toml', 'rb') as f:
config = tomllib.load(f)
print(config['project']['name'])
print(config['tool']['pytest']['ini_options'])
```
### 5. Performance Improvements
```python
# 10-60% faster than Python 3.10
# Key improvements:
# - Specialized adaptive interpreter
# - Better inline caching
# - Faster function calls
# - Optimized built-in methods
# Benchmark:
import timeit
# Dict operations are significantly faster
timeit.timeit('d["key"] = value', setup='d = {}; value = 1', number=1_000_000)
# 3.10: ~0.15s
# 3.11: ~0.09s (40% faster)
# Function calls
timeit.timeit('foo()', setup='def foo(): pass', number=10_000_000)
# 3.10: ~0.8s
# 3.11: ~0.6s (25% faster)
```
---
## Python 3.12 (October 2023)
### 1. Free-Threading (No-GIL) Preview
Python 3.12 introduced the option to build CPython without the GIL.
```bash
# Build Python without GIL (experimental)
./configure --disable-gil
make
# Or use pre-built free-threading builds
# On Linux: python3.12 -X novalgrind script.py
```
```python
# Check if GIL is enabled
import sys
print(sys._is_gil_enabled()) # True in standard builds
# With free-threading, CPU-bound threads run truly in parallel
from concurrent.futures import ThreadPoolExecutor
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(1_000_000):
with lock:
counter += 1
# Without GIL: truly parallel
threads = [threading.Thread(target=increment) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # 4_000_000 (much faster than with GIL)
```
### 2. F-String Improvements (PEP 701 Preview)
```python
# Python 3.12 allows unescaped quotes inside f-strings
name = "O'Reilly"
print(f"Hello, {name}") # Works in 3.12+ (was error in earlier versions)
# Self-documenting f-strings with = (3.8+) improved
x = 42
print(f"{x=}") # x=42
print(f"{x=:#x}") # x=0x2a
print(f"{result:=10}") # result= 42
```
### 3. Type Parameter Syntax Preview (PEP 695 Preview)
```python
# Preview of upcoming generic syntax
def first[T](items: list[T]) -> T:
return items[0]
class Stack[T]:
def __init__(self):
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
```
### 4. Removed Deprecated Features
```python
# asyncio.get_event_loop() replaced by asyncio.run()
# OLD (deprecated):
loop = asyncio.get_event_loop()
loop.run_until_complete(coro)
# NEW:
asyncio.run(coro)
# pkgutil.extend_path removed
# imp module removed (use importlib)
# distutils removed (use setuptools or packaging)
```
### 5. `tomllib` Added, `tomli` No Longer Needed
```python
import tomllib # Built-in TOML parser (read-only)
# Write TOML requires third-party: pip install tomlkit
```
---
## Python 3.13 (October 2024)
### 1. Type Parameter Syntax (PEP 695) — NOW AVAILABLE
```python
# New generic syntax (no more TypeVar needed for simple cases)
def first[T](items: list[T]) -> T:
return items[0]
# Equivalent old syntax:
from typing import TypeVar
T = TypeVar('T')
def first_old(items: list[T]) -> T:
return items[0]
# Class generics
class Container[T]:
def __init__(self, value: T):
self.value = value
def get(self) -> T:
return self.value
# Multiple type parameters
def merge[K, V](dict1: dict[K, V], dict2: dict[K, V]) -> dict[K, V]:
result = dict1.copy()
result.update(dict2)
return result
# Bounded type parameters
def sort_items[T: Comparable](items: list[T]) -> list[T]:
return sorted(items)
# Where-clause style constraints (using Protocol)
from typing import Protocol
class HasLen(Protocol):
def __len__(self) -> int: ...
def get_length[T: HasLen](item: T) -> int:
return len(item)
```
### 2. Type Alias Statement (PEP 696 Preview)
```python
# New type alias syntax
type StringOrInt = str | int
type NumberList = list[int] | list[float]
# With type parameters
type Box[T] = tuple[T, T]
type Result[T, E] = tuple[T, None] | tuple[None, E]
# Using the aliases
def process(value: StringOrInt) -> None:
...
def transform(data: NumberList) -> NumberList:
return [x * 2 for x in data]
```
### 3. Per-Interpreter GIL (PEP 703)
Multiple interpreters, each with their own GIL, running in parallel.
```python
import _xxinterpchannels as channels
import _xxinterpqueues as queues
import _interpreters
# Create a new subinterpreter
interp_id = _interpreters.create()
# Run code in the subinterpreter
def worker():
result = sum(i * i for i in range(1_000_000))
return result
interpreters.run_in_interpreter(interp_id, worker)
# Channel-based communication
send_ch, recv_ch = channels.new_channel()
def sender():
send_ch.send("Hello from subinterpreter!")
interpreters.run_in_interpreter(interp_id, sender)
message = recv_ch.recv()
print(message) # Hello from subinterpreter!
```
### 4. Compile-Time F-String Evaluation (PEP 750)
```python
# F-string expressions are now evaluated at compile time when possible
# This allows more optimizations
# Constants are pre-computed
message = f"Hello, {'World'}" # Compiled to: "Hello, World"
# Complex expressions with known values
x = 42
y = f"The answer is {x}" # x resolved at compile time if inlined
# Error detection at compile time
# f"{unknown_var}" # NameError at COMPILE time, not runtime
```
### 5. Static Assertion (PEP 727)
```python
# Assert conditions at compile time
assert True, "This always passes" # Runtime check
# Static assertions (3.13+):
# assert sys.version_info >= (3, 13), "Requires Python 3.13+"
# Evaluated at module import time
# Useful for:
# - Minimum version requirements
# - Platform checks
# - Feature availability
```
### 6. Performance Improvements
```python
# 10-25% faster than Python 3.12
# Key improvements:
# - Faster startup (frozen stdlib modules)
# - Improved JIT-like optimizations
# - Better memory allocation
# Frozen stdlib: frequently used modules are "frozen" (compiled to bytecode)
# This speeds up imports:
import sys
print(sys._stdlib_module_names) # Shows frozen modules
```
### 7. Other Notable Changes
```python
# 1. sys.monitoring API — for debuggers and profilers
import sys
# Low-level API for instrumentation
# 2. Improved warnings
# DeprecationWarning now visible by default in more contexts
# 3. New `typing` features
from typing import TypeIs, TypeGuard
# Type narrowing with TypeIs
def is_string_list(val: list[object]) -> TypeIs[list[str]]:
return all(isinstance(v, str) for v in val)
def process(val: list[object]):
if is_string_list(val):
reveal_type(val) # list[str] — narrowed!
# 4. Better error messages for common mistakes
# Trying to use | on incompatible types now gives clear errors
```
---
## Feature Comparison Table
| Feature | 3.10 | 3.11 | 3.12 | 3.13 |
|---------|------|------|------|------|
| Pattern matching | ✅ | ✅ | ✅ | ✅ |
| `str \| int` types | ✅ | ✅ | ✅ | ✅ |
| Exception groups | | ✅ | ✅ | ✅ |
| `tomllib` | | ✅ | ✅ | ✅ |
| Better error messages | | ✅ | ✅ | ✅ |
| Free-threading | | | Preview | Stable |
| Type params `def f[T]` | | | Preview | ✅ |
| Type aliases `type X =` | | | | ✅ |
| Per-interpreter GIL | | | | ✅ |
| Compile-time f-strings | | | | ✅ |
| Static assertions | | | | ✅ |
| Performance | baseline | +10-60% | +10-25% | +10-25% |
---
## Migration Guide
### Upgrading from 3.9 to 3.13
```python
# 1. Replace Union/Optional with | syntax
# OLD:
from typing import Union, Optional
def foo(x: Union[str, int], y: Optional[str]) -> None: ...
# NEW:
def foo(x: str | int, y: str | None) -> None: ...
# 2. Replace TypeVar with type parameters
# OLD:
from typing import TypeVar
T = TypeVar('T')
def first(items: list[T]) -> T: return items[0]
# NEW:
def first[T](items: list[T]) -> T: return items[0]
# 3. Replace typing aliases
# OLD:
from typing import TypeAlias
StringOrInt: TypeAlias = str | int
# NEW:
type StringOrInt = str | int
# 4. Update asyncio usage
# OLD:
loop = asyncio.get_event_loop()
loop.run_until_complete(coro)
# NEW:
asyncio.run(coro)
# 5. Replace deprecated modules
# OLD: from distutils import version
# NEW: from packaging import version
# 6. Update TOML parsing
# OLD: import toml (third-party)
# NEW: import tomllib (stdlib, read-only)
```