Python 3.11: Significant Speed Improvements
Python 3.11 is here, and it’s fast. The Faster CPython project, led by Microsoft-funded developers including Guido van Rossum, delivered real performance improvements. Here’s what changed.
The Numbers
Official benchmarks:
- 10-60% faster than Python 3.10
- Average improvement: 25%
- Some workloads: 50%+ improvement
# Simple benchmark
python3.10 -m timeit "sum(range(1000000))"
# 10 loops, best of 5: 25.3 msec per loop
python3.11 -m timeit "sum(range(1000000))"
# 10 loops, best of 5: 17.8 msec per loop (~30% faster)
What Made It Faster
1. Specializing Adaptive Interpreter
Python 3.11 watches bytecode execution and optimizes hot paths:
def add_numbers(a, b):
return a + b
# First few calls: generic BINARY_OP
# After detecting int+int pattern: BINARY_OP_ADD_INT
The interpreter specializes operations based on observed types.
2. Cheaper Exceptions (Zero-Cost)
# Pre-3.11: try/except has overhead even when no exception
try:
result = dict_lookup[key] # Cost even if key exists
except KeyError:
result = default
# 3.11: Zero-cost try blocks
# No overhead unless exception actually raised
Now the “Easier to Ask Forgiveness” pattern is genuinely free.
3. Faster Function Calls
Stack frames are cheaper:
# Recursive function benchmark
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
fib(30) # Significantly faster in 3.11
~25% faster function call overhead.
4. Inlined Python Function Calls
# Simple functions may be inlined
def square(x):
return x * x
result = square(5) # May not create full stack frame
Better Error Messages
This isn’t performance, but it’s a game-changer:
Before (3.10)
d = {"a": {"b": None}}
print(d["a"]["b"]["c"])
# Traceback:
# TypeError: 'NoneType' object is not subscriptable
After (3.11)
d = {"a": {"b": None}}
print(d["a"]["b"]["c"])
# Traceback:
# print(d["a"]["b"]["c"])
# ~~~~~~~~^^^^^
# TypeError: 'NoneType' object is not subscriptable
The caret points to exactly which part failed.
Complex Expressions
x = [1, 2]
y = [3, 4]
print(x[0] + y[2] + x[1])
# print(x[0] + y[2] + x[1])
# ~~~~^^^
# IndexError: list index out of range
Debug time reduced significantly.
Exception Groups
New feature for handling multiple exceptions:
# Raise multiple exceptions together
def validate_data(data):
errors = []
if not data.get('name'):
errors.append(ValueError("name required"))
if not data.get('email'):
errors.append(ValueError("email required"))
if errors:
raise ExceptionGroup("Validation failed", errors)
# Handle with except*
try:
validate_data({})
except* ValueError as e:
print(f"Got {len(e.exceptions)} ValueError(s)")
except* TypeError as e:
print(f"Got TypeError(s)")
Particularly useful for async code with multiple failures.
TaskGroup for Asyncio
import asyncio
async def fetch_all():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_url("http://example.com"))
task2 = tg.create_task(fetch_url("http://example.org"))
task3 = tg.create_task(fetch_url("http://example.net"))
# All tasks complete when context exits
# Exceptions are collected as ExceptionGroup
return [task1.result(), task2.result(), task3.result()]
Cleaner than asyncio.gather() with better error handling.
TOML Support
import tomllib # New in 3.11
with open("pyproject.toml", "rb") as f:
config = tomllib.load(f)
print(config["project"]["name"])
Finally, native TOML parsing. No more third-party packages for basic config reading.
Typing Improvements
Self Type
from typing import Self
class Builder:
def set_name(self, name: str) -> Self:
self.name = name
return self
def set_value(self, value: int) -> Self:
self.value = value
return self
# Correct type inference for subclasses too
class ExtendedBuilder(Builder):
def set_extra(self, extra: str) -> Self:
self.extra = extra
return self
TypeVarTuple
from typing import TypeVarTuple
Ts = TypeVarTuple('Ts')
def concat(*args: *Ts) -> tuple[*Ts]:
return args
# Type-safe variadic generics
Required and NotRequired
from typing import TypedDict, Required, NotRequired
class User(TypedDict, total=False):
name: Required[str]
email: Required[str]
age: NotRequired[int]
Migration Notes
Compatibility
Most code runs unchanged. Some edge cases:
# object.__getstate__ behavior changed slightly
# Some C API changes affect extensions
# Update your Cython, NumPy, etc.
Check Your Dependencies
pip install --upgrade numpy pandas
# Ensure compatible versions for 3.11
Real-World Impact
Django
Reports of 10-25% faster request handling for typical views.
Data Processing
# Pandas operations
df = pd.read_csv("large.csv")
result = df.groupby("category").sum()
# 15-20% faster in 3.11
CLI Tools
Faster startup time means snappier command-line tools.
Should You Upgrade?
Yes, if:
- Dependencies support 3.11
- You want free performance boost
- Better error messages appeal to you
Wait, if:
- Critical dependencies don’t support it yet
- You’re on a very old codebase (check compatibility)
Final Thoughts
Python 3.11 delivers on the promise of making Python faster without breaking compatibility. The Faster CPython project continues—3.12 and beyond will bring more.
Upgrade when your dependencies are ready. The performance gains are real.
10-60% faster, and the error messages actually help.