What's new in Python 3.10?

Structural pattern matching, parenthesized context managers, improved error messages, and more.

  • By Tushar
  • ·
  • Insights
  • Python
Last updated Sep 17, 2021

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 of 1 bits in a number's binary representation.
  • Other typing module additions: the TypeAlias type, and user defined type guards with TypeGuard.
  • Added sys.orig_argv property, which contains the original args passed to the python 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. 👀

Ship clean and secure code.