Alexander Hultnér Get started with Pattern Matching in Python, today!

What is structural pattern matching ?

Structural pattern matching is a new way to dispatch code based on data.

Some of you have done similar things using techniques such as multiple function/method dispatch or with the visitor pattern.

I prefer function dispatch of the two, but pattern matching is how I think.

Pattern matching originates from functional languages;

Scala heavily inspires the Python version. I have very shallow experience with Scala, but similar concepts exist in Haskell, Erlang and Elixir, where I’ve used and loved their power.

If you are anything like me, you’ve seen patterns all your life, starting with wooden shape fitting toys, puzzles and Legos.

We can also express these as different kinds of pattern matching.

It makes sense that python should enable us to translate this way of thinking to code seamlessly. On with it, how does it look in code?

status_code = 200
match status_code:
    case 404: print("Not found")
    case 200: print("Ok")
    case _: raise ValueError("Can't handle status code")
# Prints "Ok"

It is straightforward to grasp how pattern matching works here. You may ask, how is this different from “switch-case” statements, commonly found in C-influenced languages? You’re right.

In this case, it is not.

That’s because I haven’t covered the structural part of structural pattern matching yet . The real power lies beyond value matching.

The structural component is similar to the destructuring syntax. Which is, in its turn, meant to emulate object creation but used in reverse.

Let’s see an example to make that clear.

 1 from dataclasses import dataclass
 2
 3 @dataclass
 4 class Point:
 5     x: int
 6     y: int
 7
 8 def where_is(point):
 9     match point:
10         case Point(x=0, y=0):
11             print("Origin")
12         case Point(x=0, y=y):
13             print(f"Y={y}")
14         case Point(x=x, y=0):
15             print(f"X={x}")
16         case Point(x=x, y=y):
17             print(f"Somewhere else, X={x}, Y={y}")
18         case [Point(), Point(), Point()] as triangle:
19             print("Triangle:")
20             for p in triangle: where_is(p)
21         case [*shapes]:
22             print("Multiple points:")
23             for shape in shapes: where_is(shape)
24         case _:
25             print("Not a point")
26
27
28 shapes = [
29     Point(x=0, y=0),
30     Point(x=1, y=0),
31     Point(x=0, y=1),
32     Point(x=1, y=1),
33     [
34         Point(x=0, y=0),
35         Point(x=1, y=0),
36         Point(x=0, y=1),
37     ],
38 ]
39
40 where_is(shapes)
>>>
>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class Point:
...     x: int
...     y: int
...
>>> def where_is(point):
...     match point:
...         case Point(x=0, y=0):
...             print("Origin")
...         case Point(x=0, y=y):
...             print(f"Y={y}")
...         case Point(x=x, y=0):
...             print(f"X={x}")
...         case Point(x=x, y=y):
...             print(f"Somewhere else, X={x}, Y={y}")
...         case [Point(), Point(), Point()] as triangle:
...             print("Triangle:")
...             for p in triangle: where_is(p)
...         case [*shapes]:
...             print("Multiple points:")
...             for shape in shapes: where_is(shape)
...         case _:
...             print("Not a point")
...
>>>
>>> shapes = [
...     Point(x=0, y=0),
...     Point(x=1, y=0),
...     Point(x=0, y=1),
...     Point(x=1, y=1),
...     [
...         Point(x=0, y=0),
...         Point(x=1, y=0),
...         Point(x=0, y=1),
...     ],
... ]
>>>
>>> where_is(shapes)


>> where_is(shapes)
Multiple points:
Origin
X=1
Y=1
Somewhere else, X=1, Y=1
Triangle:
Origin
X=1
Y=1

I’ve based this example on an excerpt from the official tutorial, PEP 636.

Hang on, and the following example showcases a few of my favourite structural parts of pattern matching. First, we can see that it matches a point in a predefined position.

Then we proceed to extract variables from the point. The way we write this is so you can reconstruct the original object with the same statement.

Destructuring of iterables works in the same way . I especially like how python is consistent in this way.

Now to the even more fun part. In the fourth match, you can see how easily we create a recursive function here. We call “where_is()” again for all the points of a triangle. But we’re not done yet.

Next, we can provide a list of shapes, which is then print. Notice how we only call where_is one time in the end, which is immensely powerful for writing elegant, simple parsers and makes it easy to express advanced algorithms in a readable manner.

It’s going to take some time to get used to structural pattern matching. But I think you’ll love it once you have.

Questions about pattern matching

If you have any more questions, I’ll do my best to answer them.

I watched a talk by Daniel Moisett, https://fediverse.org/dmoisset on Twitter, at the Python Amsterdam Meetup yesterday (March 10 2021).

Moisset is one of the authors of the pattern matching PEP trilogy, and I highly recommend watching the whole hour if you want to dive deeper.

In this talk, he answered one question I’ve had since first reading the PEPs. The question follows, why isn’t pattern matching allowed in overloaded function definitions? That is multiple function definitions of the same function. In this case, dispatch handled by patterns of function arguments.

Daniel’s answer to this question makes a lot of sense to me. In a gist, pattern matching is already a significant change, and function definitions are already involved. At this point, it’s better to do the minimum viable implementation than to implement a heard of changes at once.

Pattern matching in function definitions is not an impossibility at a later date. But there’s no harm in saving that for later if we see a desire.

Another question I liked, it’s something I didn’t even think about at all myself being used to pattern matching in functional languages.

Why is there no else clause? The answer is quite simple. There was disagreement with about a 50/50 split whenever to have them at the same indention level as the match statement or the case level.

The wild-card and “don’t care” pattern already covers this case, so they omitted it for now, with the possibility to add it in a later python version.

I love this about python. It is better to wait a little longer before implementing additional features than adding them and regretting the choices later.

Let’s focus on getting pattern matching out in the world now and see how people use it.

Learn more

I recommend first looking at the official tutorial PEP 636.

If you haven’t gotten enough, you can see the formal specification in PEP 634 and its motivations in PEP 635. I’m also planning to write a follow up to this article if it’s well-received, so stay tuned for updates.

I might even do a talk for a future PyCon.

I suggest watching Daniel Moisset’s talk from the Python Amsterdam Meetup referenced earlier as well.

I do enterprise workshops, lectures and training, as well as freelance consulting via Hultnér Technologies. Don’t hesitate to contact me!