What Is New in Python 3.11
Python 3.11 is the fastest CPython release to date at its time -- up to 25% faster than 3.10 on the pyperformance benchmark suite. The key driver is a new specializing adaptive interpreter that replaces generic bytecode with type-specific opcodes at runtime. On the language side, exception groups with except* land for concurrent error handling, and the typing system gains Self, variadic generics, and LiteralString.
| Category | Change | PEP / Reference |
|---|---|---|
| Performance | Specializing adaptive interpreter -- up to 25% faster than 3.10 | PEP 659 |
| New Syntax | Exception groups and except* for parallel error handling |
PEP 654 |
| Typing | Self type for methods returning their own instance |
PEP 673 |
| Typing | Variadic generics: TypeVarTuple and Unpack |
PEP 646 |
| Typing | LiteralString for injection-safe string APIs |
PEP 675 |
| New Modules | tomllib -- TOML parsing in stdlib |
PEP 680 |
| Error Messages | Fine-grained tracebacks -- exact expression highlighted, not just the line | -- |
| asyncio | asyncio.TaskGroup for structured concurrent task management |
-- |
| Standard Library | math.exp2(), math.cbrt(); statistics.covariance(), correlation() |
-- |
| Deprecated | distutils, asynchat, asyncore, smtpd |
-- |
How Does the Specializing Adaptive Interpreter Make Python 3.11 Faster?
The interpreter watches bytecode execution and replaces generic opcodes with faster type-specific variants when it detects a consistent type pattern. For example, BINARY_OP becomes BINARY_OP_ADD_INT when both operands are consistently integers. If the type changes, it de-specializes and watches again.
- Arithmetic on int/float: specialized opcodes skip type dispatch entirely.
- Attribute access: per-type inline caches avoid dict lookup on hot paths.
- Function calls:
CALLbecomesCALL_PY_EXACT_ARGSfor calls with matching argument counts, skipping argument coercion. super()without arguments: compiled to a zero-argument fast path in single-inheritance code.
The gains are most visible in code that is hot enough to specialize but not memory-bound. Web frameworks see 15-30% faster request handling in benchmarks.
What Are Exception Groups and How Do You Use except*?
ExceptionGroup bundles multiple exceptions into a single raised object -- critical for concurrent code where several tasks can fail simultaneously. except* matches specific exception types within a group and can match multiple branches independently.
import asyncio
async def fetch(url):
... # may raise ConnectionError or ValueError
async def main():
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(fetch("https://a.example"))
tg.create_task(fetch("https://b.example"))
except* ConnectionError as eg:
print(f"Network failures: {len(eg.exceptions)}")
except* ValueError as eg:
print(f"Bad data in {len(eg.exceptions)} responses")
Unlike regular except, except* does not stop processing at the first match -- it filters the group and re-raises unmatched exceptions. You can catch the same group with multiple except* arms.
What Typing Improvements Shipped in Python 3.11?
Self Type (PEP 673)
Annotating a method with Self tells type checkers the return type is the exact class the method is called on -- including subclasses. This is required for correct typing of builder patterns and method chaining.
from typing import Self
class Node:
def set_value(self, value: int) -> Self:
self.value = value
return self
class SpecialNode(Node):
pass
# Type checker infers SpecialNode.set_value() returns SpecialNode, not Node
LiteralString (PEP 675)
LiteralString marks a parameter as accepting only string literals or values derived from string literals -- not arbitrary user input. This lets type checkers flag potential SQL or shell injection at analysis time.
from typing import LiteralString
def run_query(sql: LiteralString) -> list:
...
run_query("SELECT * FROM users") # OK
run_query(user_input) # type error -- might be tainted
tomllib -- TOML Parsing in stdlib (PEP 680)
tomllib parses TOML files (read-only -- no TOML writer). It is what replaced the third-party tomli package that many projects already depended on. pyproject.toml tools no longer need a runtime dependency for parsing their own config file.
import tomllib
with open("pyproject.toml", "rb") as f:
config = tomllib.load(f)
print(config["project"]["name"])
What Were the Fine-Grained Traceback Improvements?
Python 3.11 adds column information to tracebacks. The caret (^) now points to the specific sub-expression that failed, not just the line. For chained attribute access or complex expressions, this makes the error immediately obvious without needing a debugger.
Traceback (most recent call last):
File "t.py", line 1, in <module>
result = data["key"]["nested"]
~~~~~~~~~~~^^^^^^^^^^
KeyError: 'nested'
The tildes (~) underline the portion of the expression that succeeded, and the carets show exactly where it failed. This is especially useful in long expression chains.
FAQ
Is the 25% speedup in Python 3.11 consistent across all workloads?
No. The pyperformance benchmark shows 25% on average, but individual workloads vary widely. CPU-bound numerical code with tight integer loops can see 40-60% gains. I/O-bound code -- where the bottleneck is network or disk -- sees little benefit because execution time is dominated by waiting, not bytecode interpretation.
Can except* be used with non-concurrent code?
Yes. You can raise an ExceptionGroup manually in any code, not just async code. For example, a batch processor that collects all validation errors before raising is a good non-async use case. The idiom is: collect errors in a list, then raise ExceptionGroup("validation failed", errors) at the end.
Does tomllib support writing TOML, not just reading?
No. tomllib is read-only by design. For writing TOML, use the third-party tomli-w package. The decision to include only a reader was deliberate -- TOML writing is controversial (comment preservation, key ordering) and the stdlib team wanted to avoid the complexity.
How does LiteralString interact with f-strings?
F-strings with only literal parts (f"SELECT * FROM {table_name}") are NOT LiteralString when table_name is a variable -- even if the variable itself comes from a literal. Only bare string literals and their concatenations satisfy the constraint. This is intentional: f-strings with variables can embed arbitrary input.
Does the specializing adaptive interpreter require any code changes to benefit?
None. The optimization happens entirely inside CPython at runtime. Any Python 3.11 interpreter running existing code will automatically profile and specialize hot paths. The only way to prevent it is to disable it with a compile flag, which you would not do in production.