It’s finally happened, after years of complaining and told “it will never happen”, I got my darn switch statement! And honestly, calling it only a “switch” statement is really underselling it. It’s much more powerful than most people will ever use! You might have already heard about this, but I wanted to make sure it made it to at least the 3.10 beta phase, as the alphas are no guarantee you’ll actually see the new feature (still possible to remove before the RC phase, but less likely).
Python 3.10 will introduce “Structural Pattern Matching” as introduced in PEP622 which is a crazy advanced switch statement that can recognizing patterns. Instead of the keyword switch
Python will introduce match
instead (get ready to update your regex variable names!). Let’s do a little quick compare with it and JavaScript’s switch statement. First let’s start off with the Javascript example modified from w3schools.
Javascript’s Switch
switch (new Date().getDay()) { case 5: console.log("It's Friday!"); break; case 0: case 6: console.log("Woo, Weekend!"); break; default: console.log("Work. Work. Work. Work."); }
Python’s Structural Pattern Matching
We can accomplish the same with Python in less lines. Note that in Python the weekday
starts at 0 for Monday.
from datetime import datetime match datetime.today().weekday(): case 4: print("It's Friday!") case 5 | 6: print("Woo, Weekend!") case _: print("Work. Work. Work. Work.")
Similarities and Differences
With Python’s new match
style statements, the biggest change from tradition is no “drop through” cases. Aka you don’t have to manually specify break
or anything to exit the match
case
for every instance. That does mean though that you can’t have multiple matches with the same result. Instead you can combine checks with |
for “or” operations. Which I honestly think is a lot cleaner.
What I don’t like is there is no default
keyword or similar, and you have to use the _
wildcard. The underscore is already overused in my opinion. However it does add a lot of power for more advanced cases
. Let’s dive into some more examples.
Matching and Assigning
The very basic things to understand about the new Structural Pattern Matching is the flow (as seen in the example above) and the possibility of assignment. Because not only can you make sure things are equal to another literal, you can detect patterns (as the name suggests) and then work with the variables inside the pattern itself.
Pattern Recognition
Lets dip the toes in on this whole “pattern matching” thing, what does that mean?
Say you have a tuple with two objects, that could be in either order and you always want to print it right.
data_1 = ("Purchase Number", 574) data_2 = (574, "Purchase Number")
You could have a simple match case to straighten it out so it always prints “Purchase Number 574”. As the pattern matching support type checking.
match data_1: case str(x), int(y): print(x, y) case int(x), str(y): print(y, x)
Match on dict values
Just like with regular value comparisons, you can check dictionary values and still do or
operations. In this case, either grade b
or c
will print out "Welcome to being average!"
match {'grade': 'B'}: case {'grade': 'a' | 'A'}: print("You're a star!") case {'grade': 'b' | 'c' | 'B' | 'C'}: print("Welcome to being average!")
Unpacking arbitrary data
You can use the standard *
and **
unpackers to capture and unpack variable length lists and dicts.
my_dict = {'data': [{'action': 'move'}, {'action': 'stay'}], 'location': "square_10"} match my_dict: case {'data': [*options], **remainder }: print(options) print(remainder) case _: raise ValueError("Data not as expected")
Will match with the first case and print out:
[{'action': 'move'}, {'action': 'stay'}] {'location': 'square_10'}
That can really come in handy if dealing with variable length data.
Adding the “if”
You can also add in some inline checks.
cords = (30.25100365332043, -97.86221371827051, "Workplace") cords_2 = (44.40575746530253, 8.947747627912994) match cords: case lat, lon, *_ if lat > 0: print("Northern Hemisphere") case lat, lon, *_ if lat < 0: print("Sothern Hemisphere")
Don’t forget about “as”
Sometimes you might want one the many literals you are looking for to be usable in the case itself. Lets put a lot of our knowledge together and check out the added power of the as
clause.
We are going to build a really simple command line game to look for random things.
import sys import random inventory = [] while True: match input().lower().split(): # Remember `split` makes the input into a list. # These first two cases are functionally the same. case ["exit"]: sys.exit(0) case "stop", *_: sys.exit(0) case "travel", "up" | "down" | "left" | "right" as direction: print(f"Going {direction}") case "search", "area" | "backpack" as thing, _, *extra: item = " ".join(extra) if thing == "backpack": if item in inventory: print(f"Yes you have {item}") else: print(f"No you don't have {item}") elif thing == "area": if random.randint(1, 10) > 5: inventory.append(item) print(f"You found {item}!") else: print(f"No {item} here") else: print(f"Sorry, you cannot search {thing}") case _: print("sorry, didn't understand you! type 'exit' or 'stop' to quit")
Lets do a quick playthrough:
> search area for diamonds You found diamonds! > search backpack for diamonds Yes you have diamonds > take a short rest and try to eat the diamonds sorry, didn't understand you! type 'exit' or 'stop' to quit > Travel Up Going up > search area for candy No candy here > search backpack for candy No you don't have candy > stop # Process finished with exit code 0
Notice our special case for searching around.
case "search", "area" | "backpack" as thing, _, *extra:
we are looking for the first keyword “Search”, then either “area” or “backpack” and saving which to the variable thing
. The _
will ignore the next word as we expect them to type for
or something useless there. Finally we grab everything else and treat it as a single item.
Classes and More
Using dataclasses
with match
is super easy.
from dataclasses import dataclass @dataclass class Car: model: str top_speed: int car_1 = Car("Jaguar F-Type R", 186) car_2 = Car("Tesla Model S", 163) car_3 = Car("BMW M4", 155) match car_1: case Car(x, speed) if speed > 180: print(f"{x} is a super fast car!") case Car(x, speed) if speed > 160: print(f"{x} is a pretty fast car") case _: print("Regular Car")
Using a regular class, you have to be a pit more explicit about the varaibles that are being used.
class Car: def __init__(self, model, top_speed): self.model = model self.top_speed = top_speed car_1 = Car("F-Type R", 186) car_2 = Car("Model S", 163) car_3 = Car("BMW M4", 155) match car_1: case Car(model=x, top_speed=speed) if speed > 180: print(f"{x} is a super fast car!") case Car(model=x, top_speed=speed) if speed > 160: print(f"{x} is a pretty fast car") case _: print("Regular Car")
However, there is a way around that! You can add __match_args__
to the class itself to define which arguments you will want to use for the pattern recognition.
class Car: __match_args__ = ["model", "top_speed"] def __init__(self, model, top_speed): self.model = model self.top_speed = top_speed car_1 = Car("F-Type R", 186) car_2 = Car("Model S", 163) car_3 = Car("BMW M4", 155) match car_1: case Car(x, speed) if speed > 180: print(f"{x} is a super fast car!") case Car(x, speed) if speed > 160: print(f"{x} is a pretty fast car") case _: print("Regular Car")
Summary
Structural Pattern Matching is Python’s sexy new “switch” statement that will bring a lot of power, and removal of a lot of old if
elif
blocks. The only real downside is that it’s brand new, and will take a few years for most people to be able to use it with their default interpreter.