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.
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.
# 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.
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.
# 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.
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
remodule supports atomic groups ((?>...)) and possessive quantifiers (*+,++). StrEnum,IntEnum, andReprEnumare enhanced in theenummodule.- The
hashlibmodule supports file-level BLAKE2 algorithms. - The
mathmodule gainsmath.cbrt()for cube roots andmath.exp2(). - Interpreter startup is 10-15% faster.
Deprecations and Removals
Notable changes in this category:
- The
asynchatandasyncoremodules are removed after being deprecated since Python 3.6. - The
st_ctimefields on Windows change semantics (creation date instead of metadata modification date). - Several deprecated
unittestmodule functions are removed. - The
aifcmodule is deprecated and scheduled for removal in Python 3.13.
