What's new in Python 3.10?
Structural pattern matching, parenthesized context managers, improved error messages, and more.
- By Tushar
- ·
- Insights
- Python
Python 3.10 has just been released, which means we can finally see what new features are in store. So let's take a look, shall we?
Structural pattern matching
This is a big one — It's probably the biggest syntax addition to Python since its original 3.0 release in 2008.
What "structural pattern matching" really means is that now we have a way to write conditional statements based on the structure of a piece of data, instead of logical conditions. It's done through a new match
keyword, and multiple case
clauses:
status = get_http_status()
match status:
case 200:
print('OK')
case 404:
print('Not Found')
case _:
print(f'Unknown status: {status}')
You might think that this looks very similar to another popular feature in other languages: switch statements. And you're right — it can absolutely be used as a replacement for switch-case, but match
does a lot more. Here are a few examples:
Matching elements in a data structure:
command = ['move', 'right']
match command:
case ['move', direction]:
print(f'Player moved in {direction} direction.')
case ['attack', enemy]:
print(f'Player attacked {enemy}.')
case _:
print('Unknown command.')
# Output:
# Player moved in right direction.
Matching properties in an object:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
vec = Vector(3, 5)
match vec:
case Vector(x, 0):
print(f'Vector lies on Y-axis with {x=}')
case Vector(0, y):
print(f'Vector lies on X-axis with {y=}')
case Vector(x, y):
print(f'Vector lies on {x=}, {y=}')
# Output:
# Vector lies on x=3, y=5
Matching variable number of properties using spread syntax:
msg = {'type': 'collect', 'orbs': 10, 'badges': 30}
match msg:
case {'type': 'collect', **bag}:
print('Collected items:')
for name, count in bag.items():
print(f'{name} - {count}')
case {'type': 'drop', **bag}:
print(f"Dropped items: {', '.join(bag)}")
case _:
print('Unknown message')
# Output:
# Collected items:
# orbs - 10
# badges - 30
You can find more examples in the "What's new" page and in PEP 636.
Better error messages
As friendly as Python is in terms of readability, its error messages can sometimes be pretty hard to understand. Fortunately, Python 3.10 brings huge improvements in the readability of its error messages.
Some of these improved messages are:
>>> if rocket.position = event_horizon:
File "<stdin>", line 1
if rocket.position = event_horizon:
^
SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='?
>>> collections.namedtoplo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'collections' has no attribute 'namedtoplo'. Did you mean: namedtuple?
>>> values = {x:1, y:2, z w:3}
File "<stdin>", line 1
values = {x:1, y:2, z w:3}
^
SyntaxError: ':' expected after dictionary key
>>> foo(x, z for z in range(10), t, w)
File "<stdin>", line 1
foo(x, z for z in range(10), t, w)
^^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized
New type union operator
A very common use of the typing
module is to create union data types, i.e. variables that can access variables of multiple data types.
To do this, we needed to import typing.Union
, like so:
def double(value: Union[int, str]) -> Union[int, str]:
return value * 2
Since this was a really common use-case, a shorthand has been added for the same functionality: you can now use the |
operator to create unions out of data types:
def double(value: int | str) -> int | str:
return value * 2
scrict
parameter in the zip()
function
Python's builtin zip
function is rather handy, it takes in a bunch of iterables, and returns 1 value from each of them until any of them is out of values. For example:
nums = [1, 2, 3]
letters = ['a', 'b', 'c']
for letter, num in zip(letters, nums):
print(f'{letter}: {num}')
# Output:
# a: 1
# b: 2
# c: 3
The problem with zip
was that, if one of the iterables was longer than the other, those end values will never show up in the zip, and you'll have no way of knowing.
nums = [1, 2, 3, 4, 5, 6]
letters = ['x', 'y', 'z']
for letter, num in zip(letters, nums):
print(f'{letter}: {num}')
# Output:
# x: 1
# y: 2
# z: 3
We missed out on 4, 5, and 6 from nums
without any way of knowing that. To remedy that, the new strict
parameter has been introduced, which throws an error if any of the iterables was of a different length:
nums = [1, 2, 3, 4, 5, 6]
letters = ['x', 'y', 'z']
for letter, num in zip(letters, nums, strict=True):
print(f'{letter}: {num}')
# Output:
# x: 1
# y: 2
# z: 3
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# ValueError: zip() argument 2 is longer than argument 1
Deprecation of distutils
module
The distutils
package was the old way to create distributable Python packages, to upload on PyPI, for example. The package's functionality has been superceded by the third party packages setuptools
and packaging
. Because of this, it has been deprecated in 3.10 and will be removed in 3.12.
Other improvements
- Added
int.bit_count()
method, which returns the number of1
bits in a number's binary representation. - Other
typing
module additions: theTypeAlias
type, and user defined type guards withTypeGuard
. - Added
sys.orig_argv
property, which contains the original args passed to thepython
command.
Summary
We got some really exciting, big updates this year! For a more detailed look into the changes in this release, you can check out the docs.
Also, if you want to keep up with what's going to show up in Python next year, you can follow the developments in Python's mailing lists, and the latest open PEPs. The plans for next year already seem pretty interesting. 👀