Python 3.10 is out (and has been for a while, I’m late posting this), with new features and changes. The big new language feature this update is pattern matching. We get much better errors, the always-present typing improvements, and finally some real usage of the new PEG parser from 3.9.
Better Errors
This is the main reason you should be using 3.10+ to run your code, even if you still support older versions. Better error messages makes debugging code easier automatically, without needing libraries to start using some new feature!
Some key improvements are:
- Attribute errors / name errors now include a “did you mean?” suggestion if a
similar attribute exists (using
difflib
). - Unclosed structures now point at the start of the unclosed section, instead of at the first invalid syntax caused by the missing closer (quote or bracket).
- Syntax errors now show the entire broken syntax segment, rather than just the start.
- Many syntax errors are now are more specifically worded for the error they caught.
- Indentation errors include more context.
In addition to this, CPython now provides more precise line numbers for debuggers and other tools.
Pattern Matching
This is a huge feature and the first new block type since with blocks (2.6). It is also the first nested block type in Python. It’s a huge new feature, so let’s just look at a small example:
# Load pyproject using tomli (or tomllib in Python 3.11)
match pyproject:
case {"tool": {"mypy": {"strict": value}}}:
print(f"You have strict mode set to {value}, congrats!")
case _:
print("Ahh, buggers. No strict mode for you.")
This may look a bit like a switch statement (and that’s a subset), but it also
has several other features, some of which you see above. First, it can match
against structure, such as collections and mappings; this can be nested as well.
Second, while it can compare values by equality, it can also bind variables
(value
above) wherever they occur in the structure. Third, it matches top to
bottom (and only one), so you can add a final case _
if you want.
A few features not listed above include the ability to add guards (final if
statements), match a union of several things (denoted with |
), and capture a
portion explicitly with as
. One more feature should be mentioned explicitly:
support for classes, which can replace isinstance
. match x: case Thing():
is
identical to isinstance(x, Thing)
. You can also match on attributes with
attribute=
keyword-like syntax and positionally if the class supports it.
Read more in the what’s new page or in PEP 636, which is a tutorial PEP.
Typing improvements
As always, most of these improvements can either be imported from
typing_extensions
or can be used in 3.7+ with
from __future__ import annotations
as long as you are not using them at
runtime.
Type Unions
The biggest feature for typing is the native support for the union operator. You
can now spell Union[int, str]
as int | str
, and Optional[str]
as
str | None
. This really improves readability and removes many of the imports
needed to specify types (especially combined with 3.9’s stdlib generics). As a
bonus, this new union is usable at runtime in isinstance
in 3.10+.
Other
A new system was developed to allow libraries to add parameters to a callable (think decorators like pytest and nox provide).
A function can now be a type guard - a function that returns a boolean that is
tied to the types it sees. So you could write a function like is_int
and the
typing system will know that the argument is an int if the function returns
True.
You can use TypeAlias
to specify that a string is really a type alias and not
just a string.
Other improvements
Parenthesized context managers
Due to the new PEG parser, you can now officially add parenthesis around context managers. This is now the nicest way to apply several at a time:
with (
A() as a,
B(),
C() as c,
):
...
This was technically supported in CPython 3.9 if you were using the new parser (the default). In 3.10, the old parser has been removed.
Strict zip
zip
now has a strict
flag that you can set; this was hard to do efficiently
on arbitrary iterators any other way, and usually strict=True
is what you
want. For example:
nums = [1, 2, 3]
lets = "ABC"
for num, let in zip(nums, lets, strict=True):
print(f"{num}: {let}")
If you add or remove to either or the two lists without changing the other,
zip
will now throw an error rather than just matching the shortest list. Doing
this correctly (without iterating over the input twice) was tricky before 3.10.
Minor features
int.bit_count()
counts the number of ones in the binary representation.__ipow__
now supports__pow__
/__rpow__
fallbacks ifNotImplemented
is returned (like all the other operators).- The walrus operator supports unparenthesised usage in a few more places.
aiter()
/anext()
added to paralleliter()
/next()
.staticmethod
/classmethod
are better at wrapping now.
There were also various changes related to current and future speedups.
Functions now cache __globals__['__builtins__']
into .__builtins__
, which
sounds useful for monkeypatching in tests.
Stdlib improvements
Dataclasses
Dataclasses received a few really huge improvements in 3.10, making them more
like the attrs
library than ever.
The slots=True
option was added, which will generate __slots__
(and recreate
the class, if that matters to you).
You can also now set keyword only fields - either on the entire class or a
specific field (with kw_only=True
), or by defining a special _: KW_ONLY
field that will make subsequent fields keyword only. Besides readability and
future-proofing against rearranging attribute order, this also allows subclasses
to set defaults on attributes in a parent class! In general, mixing subclassing
and defaults is much easier with keyword-only support. For example:
@dataclasses.dataclass(kw_only=True)
class Record:
kind: str
name: str
id: int
@dataclasses.dataclass(kw_only=True)
class Movie(Record):
kind: str = "movie"
Minor features
collections.abc.Callable
andtypes.GenericAlias
improvements- Better async support in contextlib
- You can specify a
root_dir
/dir_fd
forglob
. inspect.get_annotations()
added, with a consistent way to get the annotations and unstringize. Also affectsinspect.signature()
.- Added
itertools.pairwise()
. - Several pathlib improvements related to symlinks, including
hardlink_to
, which replaceslink_to
and mimics the API ofsymlink_to
. - Several additions to
statistics
. sys.orig_argv
provides the original argv passed in (including the Python exe if present)sys.stdlib_module_names
, list of standard library names present.types.EllipsisType
/types.NoneType
/types.NotImplementedType
reintroduced for type checkers.
Deprecations, warnings, and removals
Some of the Python 2 compatibility removals that were delayed from 3.9 were
delayed one more release (3.11). There still are a nice set of new deprecations
and warnings, though! This includes a lot of the old module loading API. The
collections.*
aliases to collections.abc.*
have been removed.
There’s a new opt-in warning (-X warn_default_encoding
) if you forget to
specify the encoding when using open
. In most cases, you want
encoding="utf-8"
, rather than the default (which is now possible to specify
using "locale"
). Python 3.15 will change the default to be "utf-8"
, so you
should always specify this when using open
in text mode to be forward
compatible. And you can now force Python to use utf-8
by default with
PYTHONUTF8
envvar or -X utf8
on the command line.
Distutils is officially deprecated, slated for removal in 3.12.
Final words
Python 3.10 is a solid release, with solid improvements to errors and typing, and a fun new feature (pattern matching) to play with. It’s already powering Pyodide, a WebAssembly version of Python for your browser with compiled extension support.