Overview

Python 3.11, released on October 24, 2022, is the fastest CPython release ever. Thanks to the Specializing Adaptive Interpreter (PEP 659), many common programs run up to 60% faster than under Python 3.10, with zero code changes.

Beyond performance, this release introduces Exception Groups (ExceptionGroup and except*), the tomllib module for native TOML parsing, even more precise error messages pointing to the exact expression, and asyncio.TaskGroup for structured concurrency.

Major Features

Specializing Adaptive Interpreter (PEP 659)

The core of Python 3.11's speedup lies in an adaptive interpreter that specializes bytecode on the fly. Instead of executing generic instructions, the interpreter detects recurring types and replaces opcodes with optimized versions. For example, an addition between integers is replaced by a fast machine instruction that skips type resolution on each call.

Official benchmarks show an average 25% improvement on the pyperformance suite, with peaks of 60% on certain use cases. Web applications, data processing scripts, and CLI tools benefit directly.

python
import time

def compute_collatz(n):
    """Compute the length of the Collatz sequence for n."""
    steps = 0
    while n != 1:
        if n % 2 == 0:
            n //= 2
        else:
            n = 3 * n + 1
        steps += 1
    return steps

# This kind of numeric loop benefits strongly
# from adaptive specialization
start = time.perf_counter()
results = {i: compute_collatz(i) for i in range(1, 500_000)}
duration = time.perf_counter() - start

longest = max(results, key=results.get)
print(f"Longest sequence: n={longest}, {results[longest]} steps")
print(f"Duration: {duration:.2f}s")
# On Python 3.10: ~1.80s
# On Python 3.11: ~1.10s (about 40% faster)

Exception Groups (PEP 654)

Exception Groups (ExceptionGroup) and the except* syntax allow raising and catching multiple exceptions simultaneously. This is essential for parallel programming where multiple tasks can fail at the same time, typically with asyncio.TaskGroup.

The except* syntax filters exceptions by type within a group, and multiple except* clauses can catch different types from the same group.

python
# Creating an exception group
def validate_form(data):
    """Validate multiple fields and group all errors."""
    errors = []

    if not data.get('name'):
        errors.append(ValueError('Name is required'))
    if not data.get('email') or '@' not in data.get('email', ''):
        errors.append(ValueError('Invalid email'))
    if data.get('age') is not None and data['age'] < 0:
        errors.append(TypeError('Age must be positive'))

    if errors:
        raise ExceptionGroup('Validation errors', errors)

# Selective catching with except*
try:
    validate_form({'name': '', 'email': 'invalid', 'age': -5})
except* ValueError as val_group:
    print(f"{len(val_group.exceptions)} value error(s):")
    for e in val_group.exceptions:
        print(f"  - {e}")
except* TypeError as type_group:
    print(f"{len(type_group.exceptions)} type error(s):")
    for e in type_group.exceptions:
        print(f"  - {e}")

# Output:
# 2 value error(s):
#   - Name is required
#   - Invalid email
# 1 type error(s):
#   - Age must be positive

tomllib: native TOML parsing (PEP 680)

The tomllib module allows reading TOML files directly from the standard library, with no external dependency. TOML is the configuration format used by pyproject.toml, which has become the standard for Python project configuration. The module only supports reading; writing still requires a third-party library like tomli-w.

python
import tomllib
from pathlib import Path

# Reading a pyproject.toml file
toml_content = Path('pyproject.toml').read_bytes()
config = tomllib.loads(toml_content.decode())

project_name = config['project']['name']
version = config['project']['version']
print(f"Project: {project_name} v{version}")

# Reading directly from a file opened in binary mode
with open('pyproject.toml', 'rb') as f:
    config = tomllib.load(f)

# Accessing nested sections
dependencies = config.get('project', {}).get('dependencies', [])
for dep in dependencies:
    print(f"  Dependency: {dep}")

# Parsing a TOML string
snippet = """
[server]
host = "0.0.0.0"
port = 8000
debug = false

[database]
url = "postgresql://localhost/mydb"
pool_size = 5
"""

params = tomllib.loads(snippet)
print(f"Server: {params['server']['host']}:{params['server']['port']}")
print(f"Database: {params['database']['url']}")
# Server: 0.0.0.0:8000
# Database: postgresql://localhost/mydb

Fine-grained error locations in tracebacks

Python 3.11 significantly improves tracebacks by pointing to the exact expression that caused the error, not just the line. Visual indicators (tildes and carets) precisely underline the faulty operation. This is particularly useful when a line contains multiple calls or operations.

python
# Example with chained lookups
# result = data['users'][0]['address']['city'].upper()
#
# Traceback (most recent call last):
#   File "app.py", line 12, in process
#     result = data['users'][0]['address']['city'].upper()
#              ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
# KeyError: 'address'

# Example with arithmetic operations
# total = unit_price * quantity + shipping_fee / num_items
#
# Traceback (most recent call last):
#   File "calc.py", line 8, in compute_total
#     total = unit_price * quantity + shipping_fee / num_items
#                                    ~~~~~~~~~~~~~^~~~~~~~~~~
# ZeroDivisionError: division by zero

# Example with a method call chain
# report = generator.create().save().send()
#
# Traceback (most recent call last):
#   File "report.py", line 5, in generate_report
#     report = generator.create().save().send()
#              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
# AttributeError: 'NoneType' object has no attribute 'send'

asyncio.TaskGroup: structured concurrency

asyncio.TaskGroup provides a structured concurrency approach for asynchronous tasks. Unlike asyncio.gather(), a TaskGroup guarantees that all tasks are completed or cancelled when exiting the async with block, even on error. Exceptions are collected into an ExceptionGroup.

python
import asyncio

async def fetch_data(source, delay):
    """Simulate fetching data from a source."""
    await asyncio.sleep(delay)
    return f"Data from {source}"

async def process_in_parallel():
    """Fetch data from multiple sources in parallel."""
    results = {}

    async with asyncio.TaskGroup() as tg:
        api_task = tg.create_task(
            fetch_data('External API', 0.5)
        )
        db_task = tg.create_task(
            fetch_data('Database', 0.3)
        )
        cache_task = tg.create_task(
            fetch_data('Redis Cache', 0.1)
        )

    # All tasks are completed here
    results['api'] = api_task.result()
    results['db'] = db_task.result()
    results['cache'] = cache_task.result()

    for key, value in results.items():
        print(f"{key}: {value}")

asyncio.run(process_in_parallel())
# cache: Data from Redis Cache
# db: Data from Database
# api: Data from External API

Minor Improvements

  • The re module supports atomic groups ((?>...)) and possessive quantifiers (*+, ++).
  • StrEnum, IntEnum, and ReprEnum are enhanced in the enum module.
  • The hashlib module supports file-level BLAKE2 algorithms.
  • The math module gains math.cbrt() for cube roots and math.exp2().
  • Interpreter startup is 10-15% faster.

Deprecations and Removals

Notable changes in this category:

  • The asynchat and asyncore modules are removed after being deprecated since Python 3.6.
  • The st_ctime fields on Windows change semantics (creation date instead of metadata modification date).
  • Several deprecated unittest module functions are removed.
  • The aifc module is deprecated and scheduled for removal in Python 3.13.

Sources