Introduction
Structural Pattern Matching
is a new feature introduced in python
3.10, and perhaps the
most intriguing one. It takes as input an object to inspect (following
match
), and multiple patterns to match against (following one or
more case
). If there is a match, a pattern-specific code block
runs. The basic syntax is
Here, we check case by case if error_code
is equal to a value, if it’s
not one of 400
, 403
, 500
, _
will be a catch-all wildcard. Also,
pattern matching will break when it hits a match, so each case
should
be independent.
To run examples in this article, be sure you are using python higher than 3.10.
Pattern Matching as a Replacement for switch
Statements
The most apparent usage of pattern matching is implementing
switch ... case
statements in many other programming languages, which
is a replacement for multiple if else statements. Imagine we are
building a game, and we have a move
function for controlling figure
movement.
Match Against Dictionaries and Lists
The real power of pattern matching resides in destructuring data structures like lists and dictionaries.
In the example above, we supplied a pattern that says the object should
contains keys “first_name” and “last_name”, if the pattern matches,
their values will be captured into the variables first_name
and
last_name
. Note that for an dictionary, structural pattern matching
matches the structure instead of the exact content, the pattern is valid
as long as “first_name” and “last_name” are one of the object’s keys,
any unmentioned keys, like “middle_name”, will be ignored.
For a more advanced example, suppose we are working with the SpaceX API to retrieve launch data, an individual record of launch looks like
We only want to get the fields mission_name
, rocket_name
and
details
. Note that
Here, not only are we matching top level key mission_name
and
details
, we also use a nested pattern to extract rocket name.
You can also match on a list.
I mentioned that pattern matching for dict does not require use to
explicitly declare all fields. For lists this is different, we have to
declare all elements, this is why match 1 fails. Match 2 attempts to use
the *
syntax to capture all values after 2 into a list called rest
.
This is helpful in case the list is really large and it’s impossible to
exhaust all elements, or we simply don’t care about some elements.
However, the second pattern does not specify the correct order, as 2 is
not the first element in the matching object. Match 3 captures all
elements but the last into other
, and assign the last element to
last
.
It’s also possible to express or
logic with |
in case statement. For
example, we may add a case 401 | 403 | 404: "Not allowed"
statement to
our first example. Here is another example from pep:
Literal patterns can also be captured using the as
keyword
Add Conditions
Pattern matching allows if conditions the same way as list
comprehension, we can append an if ... else
clause like so
For conditions that checks if an object is of a data type, we can directly use the class creator function:
Here int(first) | float(first)
checks if the first element is an
integer or float. The next example uses a dataclass
based pattern as
well as its attributes
In general, any class can be used for validation. This is also called a class pattern.
Matching Against Constants
Care should be taken when matching constants, since it’s common to store
the constant in an variable and then use it in case
. The following
code will not work:
We are trying to match the value “up”, but python interprets it as a
capture pattern that says store whatever the match object is into the
variable direction
, so we are effectively overriding direction
(recall the list matching example) and the first case always matches.
Python will not allow this with an error message
SyntaxError: name capture 'direction' makes remaining patterns unreachable
.
As an alternative to matching literal values, we can use an enum type for constant matching instead