All The Important Features and Changes in Python 3.10 by Martin Heinz

Introduction

The one big feature you surely already heard about is Structural Pattern Matching. This will add case statement that we all know from other programming languages. We all know how to use case statement, but considering that this is Python - it’s not just plain switch/case syntax, but it also adds some powerful features along with it that we should explore.

Pattern matching in it’s most basic form consists of match keyword followed by expression, whose result is then tested against patterns in successive case statements:

 1 def func(day):
 2     match day:
 3         case "Monday":
 4             return "Here we go again..."
 5         case "Friday":
 6             return "Happy Friday!"
 7         case "Saturday" | "Sunday":  # Multiple literals can be combined with `|`
 8             return "Yay, weekend!"
 9         case _:
10             return "Just another day..."

In this simple example, we use day variable as our expression which is then compared with individual strings in case statements. Apart from the cases with string literals, you will also notice the last case which uses _ wildcard, which is equivalent to default keyword present in other languages. This wildcard case can be omitted though, in which case no-op may occur, which essentially means that None is returned.

Another thing to notice in the code above, is the usage of | which makes it possible to combine multiple literals using | (or) operator.

Tuple

As I mentioned, this new pattern matching doesn’t end with the basic syntax, but rather brings some extra features, such as matching of complex patterns:

1 def func(person):  # person = (name, age, gender)
2     match person:
3         case (name, _, "male"):
4             print(f"{name} is man.")
5         case (name, _, "female"):
6             print(f"{name} is woman.")
7         case (name, age, gender):
8             print(f"{name} is {age} old.")
func(("John", 25, "male"))
# John is man.

In the above snippet we used tuple as the expression to match against. We’re however not limited to using tuples - any iterable will work here.

Also, as you can see above, the _ wildcard can be also used inside the complex patterns and not just by itself as in the previous example.

Class

Using plain tuples or lists might not always is the best approach, so if you prefer to use classes instead, then this can be rewritten in the following way:

 1 from dataclasses import dataclass
 2
 3 @dataclass
 4 class Person:
 5     name: str
 6     age: int
 7     gender: str
 8
 9 def func(person):  # person is instance of `Person` class
10     match person:
11         # This is not a constructor
12         case Person(name, age, gender) if age < 18:  # guard for extra filtering
13             print(f"{name} is a child.")
14         case Person(name=name, age=_, gender="male"):  # Wildcard ("throwaway" variable) can be used
15             print(f"{name} is man.")
16         case Person(name=name, age=_, gender="female"):
17             print(f"{name} is woman.")
18         case Person(name, age, gender):  # Positional arguments work
19             print(f"{name} is {age} years old.")
func(Person("Lucy", 30, "female"))
# Lucy is woman.
func(Person("Ben", 15, "male"))
# Ben is a child.

Here we can see that it’s possible to match against class’s attributes with patterns that resemble class constructor. When using this approach, also individual attributes get captured into variables (same as with tuples shown earlier), which we can then use in respective case’s body.

Above we can also see some other features of pattern matching - in first case statement it’s a guard, which is a if conditional that follows the pattern. This can be useful if matching by value is not enough and you need to add some additional conditional check. Looking at the remaining cases here, we can also see that both keyword (e.g. name=name) and positional arguments work with this constructor-like syntax, and same also goes for _ (wildcard or “throwaway”) variable.

List

Pattern matching also allows for usage of nested patterns. These nested patterns can use any iterable, both with constructor-like objects or more iterables inside of them:

1 match users:
2     case [Person(...)]:
3         print("One user provided...")
4     case [Person(...), Person(...) as second]:  # `as var` can be used to capture subpattern
5         print(f"There's another user: {second}")
6     case [Person(...), Person(...), *rest]:  # `*var` can be used as unpacking
7         print(...)

In these kinds of complex patterns it might be useful to capture subpattern into variable for further processing. This can be done using as keyword, as shown in the second case above.

Finally, * operator can be used to “unpack” variables in the pattern, this also works with _ wildcard using the *_ pattern.

If you want to see more examples and complete tutorial, then check out PEP 636 .