What Is New in Python 3.10
Python 3.10 introduces structural pattern matching, the pipe operator for union types, parenthesized context managers, and significantly more precise error messages. Performance improves roughly 10% over 3.9 due to reduced frame overhead and optimized comprehensions.
| Category | Change | PEP / Reference |
|---|---|---|
| New Syntax | Structural pattern matching -- match/case |
PEP 634, 635, 636 |
| New Syntax | Union types with | operator: int | str |
PEP 604 |
| New Syntax | Parenthesized context managers across multiple lines | -- |
| Error Messages | Caret (^) pointing to exact error location; NameError suggests similar names |
-- |
| Typing | ParamSpec, TypeGuard, TypeAlias added to typing |
PEP 612, 647 |
| Standard Library | pathlib.Path.is_relative_to(); zip(..., strict=True) |
-- |
| Performance | ~10% average speedup over Python 3.9 | -- |
| Deprecated | distutils, parser module, array 'u' type code |
-- |
| Removed | formatter, symbol, parser modules; old C APIs |
-- |
How Does Structural Pattern Matching Work in Python 3.10?
The match statement compares a subject against a series of patterns. Unlike a switch/case in other languages, Python patterns can destructure sequences, mappings, and class instances -- not just compare literals. Guards (if) can be added to any case arm.
match command:
case ["quit"]:
quit()
case ["move", x, y]:
move_player(int(x), int(y))
case ["attack", target] if target in enemies:
attack(target)
case _:
print(f"Unknown command: {command}")
Pattern types include: literals, capture variables, sequences ([a, b, *rest]), mappings ({"key": value}), class patterns (Point(x=0, y=y)), OR patterns (p1 | p2), and the wildcard _. In practice, this replaces long if/elif chains when dispatching on data shape -- a very common pattern in parsers, command handlers, and API response processing.
What Changed About Type Hints in Python 3.10?
Union Types with the | Operator (PEP 604)
The pipe operator replaces Union[X, Y] for type annotations and works at runtime -- isinstance(x, int | str) now works without importing from typing.
# Before 3.10
from typing import Union, Optional
def parse(value: Union[int, str]) -> Optional[int]: ...
# Python 3.10+
def parse(value: int | str) -> int | None: ...
ParamSpec and TypeGuard (PEP 612, 647)
ParamSpec allows type-safe decoration of functions while preserving parameter types -- essential for writing typed decorators that pass arguments through. TypeGuard narrows types in conditional branches when a function confirms an object's type.
from typing import ParamSpec, TypeVar, Callable
P = ParamSpec("P")
R = TypeVar("R")
def logged(fn: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapper
How Did Python 3.10 Improve Error Messages?
Python 3.10 added precise column indicators -- a caret (^) under the exact offending token -- so errors show not just the line but where on the line the problem is. NameError now suggests similarly-named variables when a name is not found, and SyntaxError identifies unclosed brackets at their opening position rather than at the end of the file.
File "example.py", line 3
result = x +
^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
These changes reduce the time spent staring at error messages trying to locate the actual problem -- especially in deeply nested expressions.
What Other Language and Library Changes Landed in 3.10?
- Parenthesized context managers:
with (open("a") as f, open("b") as g):-- multi-line without backslashes. zip(..., strict=True)raisesValueErrorif iterables have different lengths -- catches a common silent bug.pathlib.Path.is_relative_to(other)and cleanerPath.relative_to().int.bit_count()-- counts set bits in an integer without converting to string.typing.TypeAliasfor explicitly declaring type aliases vs plain assignments.statistics.covariance()andstatistics.correlation()added.
What Was Deprecated or Removed in Python 3.10?
distutilsdeprecated -- migrate tosetuptoolsorbuildbefore 3.12 when it is removed.parsermodule deprecated and then removed (was already unusable since the PEG parser replaced LL(1)).formatterandsymbolmodules removed.arraytype code'u'deprecated -- usestrfor Unicode text.- Old C APIs removed:
PyEval_CallObjectand related pre-vectorcall APIs. - Passing non-path objects to
Pathmethods deprecated.
FAQ
Is match/case the same as a switch statement in other languages?
No -- it is structurally richer. A match arm can bind variables from inside a sequence or mapping (destructuring), test class shapes, and chain OR patterns. A traditional switch only compares a value to constants. The closest analogy is Scala or Rust pattern matching, not C's switch.
Can I use int | str in isinstance() at runtime, not just in type checkers?
Yes. isinstance(x, int | str) works at runtime in Python 3.10+. The | operator on types creates a types.UnionType object that isinstance and issubclass understand natively. No from typing import Union needed.
Does zip(strict=True) affect performance?
Negligibly for successful cases -- it only adds a check after all iterators are exhausted. The overhead is a single comparison at the end. For the cases where lengths differ and you want a ValueError, the savings in debugging time far outweigh any cost.
Do I need to update all Union[X, Y] annotations to X | Y immediately after upgrading to 3.10?
No. Union[X, Y] still works and is not deprecated. The | syntax is an alternative that is cleaner in new code. You can migrate gradually or keep the old style -- both are valid indefinitely.
Does parenthesized context managers support trailing commas?
Yes. with (open("a") as f, open("b") as g,): is valid -- the trailing comma follows the same rule as function arguments and collection literals. This is fine style when you add or remove managers frequently and want consistent diffs.