PY

Modern Features

Python · 8 entries

Match/Case Pattern Types

syntax
match value:
    case pattern:
        ...
example
def describe(value):
    match value:
        case int(n) if n > 0:
            return f"positive int: {n}"
        case str(s) if len(s) > 5:
            return f"long string: {s!r}"
        case [first, *rest]:
            return f"list starting with {first}, {len(rest)} more"
        case {"type": "error", "code": code}:
            return f"error code {code}"
        case _:
            return "something else"

print(describe(42))
print(describe([1, 2, 3]))
print(describe({"type": "error", "code": 500}))
output
positive int: 42
list starting with 1, 2 more
error code 500

Note Match/case supports literal, capture, sequence, mapping, class, guard (if), and OR (|) patterns. The _ wildcard matches anything.

f-string Improvements (3.12+)

syntax
f"{'quoted'} {expr!r}"
example
# Nested quotes and backslashes now allowed (3.12+)
users = [{"name": "Alice"}, {"name": "Bob"}]
print(f"First user: {users[0]['name']}")

# Multi-line f-string expressions
result = f"Total: {
    sum(
        item['price']
        for item in [{'price': 10}, {'price': 20}]
    )
}"
print(result)
output
First user: Alice
Total: 30

Note Python 3.12 lifted the restriction on backslashes and same-quote nesting inside f-string expressions. This makes many previously awkward patterns simple.

Type Parameter Syntax (3.12+)

syntax
def func[T](arg: T) -> T: ...
class Name[T]: ...
example
# New syntax (3.12+) - no more TypeVar boilerplate
def first_element[T](items: list[T]) -> T:
    return items[0]

print(first_element([10, 20, 30]))
print(first_element(["a", "b"]))

# Generic class
class Pair[A, B]:
    def __init__(self, left: A, right: B):
        self.left = left
        self.right = right

p = Pair("name", 42)
print(p.left, p.right)
output
10
a
name 42

Note The [T] syntax on def/class replaces manual TypeVar declarations. It is scoped to the function or class, unlike module-level TypeVar which could be reused accidentally.

except* for Exception Groups

syntax
try:
    ...
except* ErrorType as eg:
    ...
example
import asyncio

async def task_a():
    raise ValueError("bad value")

async def task_b():
    raise TypeError("wrong type")

async def main():
    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(task_a())
            tg.create_task(task_b())
    except* ValueError as eg:
        print(f"Value errors: {eg.exceptions}")
    except* TypeError as eg:
        print(f"Type errors: {eg.exceptions}")

# asyncio.run(main())
output
Value errors: (ValueError('bad value'),)
Type errors: (TypeError('wrong type'),)

Note except* can match multiple handlers for the same ExceptionGroup. It splits the group so each handler gets only its matching exceptions.

tomllib (Built-in TOML Parser, 3.11+)

syntax
import tomllib
with open('file.toml', 'rb') as f:
    data = tomllib.load(f)
example
import tomllib

toml_string = """
[project]
name = "myapp"
version = "1.0.0"
dependencies = ["requests", "click"]
"""

config = tomllib.loads(toml_string)
print(config["project"]["name"])
print(config["project"]["dependencies"])
output
myapp
['requests', 'click']

Note tomllib is read-only. To write TOML, use the third-party tomli-w package. Open TOML files in binary mode ('rb').

asyncio.TaskGroup (3.11+)

syntax
async with asyncio.TaskGroup() as tg:
    tg.create_task(coro())
example
import asyncio

async def fetch(url: str) -> str:
    await asyncio.sleep(0.1)
    return f"Data from {url}"

async def main():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(fetch("/api/users"))
        task2 = tg.create_task(fetch("/api/orders"))

    print(task1.result())
    print(task2.result())

# asyncio.run(main())
output
Data from /api/users
Data from /api/orders

Note TaskGroup replaces asyncio.gather() with structured concurrency. If any task raises, all other tasks are cancelled and errors are collected into an ExceptionGroup.

Performance Features (3.13+)

syntax
# JIT compiler, free-threaded mode
example
# Python 3.13 introduced:
# 1. Experimental JIT compiler (--enable-experimental-jit)
# 2. Free-threaded mode (no GIL) via python3.13t
# 3. Improved error messages with color

import sys
print(f"Python {sys.version}")

# Check if GIL is disabled (3.13+)
# sys._is_gil_enabled()  # returns False in free-threaded build

Note Python 3.13's free-threaded build removes the GIL experimentally. Most C extensions need updating to work without the GIL. The JIT is opt-in and best for CPU-bound loops.

warnings.deprecated (3.13+)

syntax
from warnings import deprecated
@deprecated("Use new_func instead")
example
import warnings

# Python 3.13+
# from warnings import deprecated
# @deprecated("Use process_v2() instead")
# def process_v1(data): ...

# For earlier versions, manual approach:
def deprecate(msg):
    def decorator(func):
        def wrapper(*args, **kwargs):
            warnings.warn(f"{func.__name__}: {msg}", DeprecationWarning, stacklevel=2)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@deprecate("Use process_v2() instead")
def process_v1(data):
    return data

process_v1("test")
output
# DeprecationWarning: process_v1: Use process_v2() instead

Note The built-in @deprecated decorator (3.13+, PEP 702) is recognized by type checkers, which will warn callers at analysis time, not just at runtime.