# 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)
```