
Published 2022-12-23 22:38:39
Alternative To if...elif...else Statements - Structural Pattern Matching In Python
image: rawpixel.com
Introduction
A switch statement is a control flow statement that allows a program to execute a different block of code based on the value of a variable or expression. It is similar to an if
statement, but it can be more concise and easier to read in certain cases.
In Python, a similar feature called structural pattern matching has been implemented since version 3.10.
Structural pattern matching allows you to match the structure of data, such as the type of an object or the shape of a data structure, and take different actions based on the result.
Structural pattern matching is performed using the match
statement, which is similar to a switch
statement in other programming languages but with some additional features and capabilities. Structural pattern matching was introduced by PEP 636. ref here
It can be used as an alternative to using if
...elif
...else
statements for performing type-based dispatch and data deconstruction. Using structural pattern matching is fairly straightforward, and this article will highlight the differences between using if
...elif
...else
statements and structural pattern matching for these purposes.
Let's dive into this and learn more about pattern matching.
Prerequisites
To be able to use structural pattern matching, you'll need to run Python 3.10 and above.
Content
The article contains the following:
- Code repo
- What Is Structural pattern matching
- Example - type-based dispatch
- Example - Literal Values In Match Patterns
- Example - Movie Menu app
- Example - Movie Streaming class
- Example - HTTP status codes matching
- Example - Switch Case with OR condition
- Example - Switch Case with AND condition
- Example - More complex pattern matching
- Example - Data Deconstruction
- Benchmarking Switch Case Statement vs If...Else statement
- Summary
Download The Code
All code shown in this article can be accessed via the following repository on GitHub:
https://github.com/devoriales/python/tree/main/pattern_matching
What Is Structural pattern matching?
Structural pattern matching is a new feature introduced in Python 3.10.
It allows you to match the structure of an object against a pattern in a concise and expressive way.
Each case statement is followed by a pattern to match against. This pattern can be something like an action and an object, like a string object: 'John'.
With structural pattern matching, we can use literal values in patterns, as well as more complex expressions. Matches are performed from top to bottom, and we can create more conditional cases with more complex patterns. The wildcard pattern, also known as the catching case, is a default case that always matches and should be placed last in the pattern as it will always execute.
The syntax for this looks like the following:
match obj
case [action]:
... # interpret single-verb action
case [action, obj]:
... # interpret action, obj
which could be something like the following example:
# determine action like obj and verb
def determine_action(x):
match x:
case (obj, verb): # data deconstruction
return f"{obj} is {verb} becase it is type {type(x)}"
case _: # default case
return f"{x} is not a tuple becase it is type {type(x)}"
print(determine_action(('dog', 'barking')))
print(determine_action(('cat', 'meowing')))
# not a tuple
print(determine_action('dog'))
Now, don't get frightened, there is some complexity in this code, i.e. data deconstruction that we will go through later. For now, it's enough to just understand that the function takes a single argument x
and returns a string based on the type and structure of x
.
Example - type-based dispatch
In the following example, we will use type-based dispatch, a technique for selecting which code to execute based on the type of an object or expression.
This can be useful when you want to perform different actions depending on the type of an object, or when you want to take advantage of the specific behavior or properties of a particular type.
We will write a function called check_type()
that takes an object as an argument and returns a message based on the type of the object.
The function provides a way to determine the type of an object in Python and return a string indicating the type.
We will look at how we can do this using both if...elif...else
and also with pattern matching.
type-based dispatch using if...elif...else
The following is an example using type-based dispatch in Python with an if
...elif
...else
statement:
# type based dispatch using if else
def check_type(x):
if type(x) == int:
return f"{x} is an integer"
elif type(x) == float:
return f"{x} is a float"
elif type(x) == list:
return f"{x} is a list"
elif type(x) == tuple:
return f"{x} is a tuple"
elif type(x) == dict:
return f"{x} is a dict"
elif type(x) == str:
return f"{x} is a string"
else:
return f"{x} is a type that I don't know about"
This code defines a function check_type
that takes an object as an argument and returns a message based on the type of the object.
The if
...elif
...else
statement checks the type of x
using the type
function, and returns a different message for each possible type.
type-based dispatch using pattern matching
Here's the same example as above, but using structural pattern matching with the match
statement:
'''
devoriales.com
Path: pattern_matching/example_0.py
description: type based dispatch using match case
'''
# type based using structural pattern matching
def check_type(x):
print('match statement is working')
match x:
case int():
return f"{x} is an integer"
case float():
return f"{x} is a float"
case list():
return f"{x} is a list"
case tuple():
return f"{x} is a tuple"
case dict():
return f"{x} is a dict"
case str():
return f"{x} is a string"
case _:
return f"{x} is a type that I don't know about"
print(check_type(1)) # 1 is an integer
print(check_type(1.0)) # 1.0 is a float
print(check_type([])) # [] is a list
print(check_type(())) # () is a tuple
print(check_type({})) # {} is a dict
print(check_type('')) # is a string
In both examples, the code is performing type-based dispatch by selecting which code to execute based on the type of the x
argument.
This code defines a function check_type
that takes an object as an argument and returns a message based on the type of the object.
The match
statement checks the type of x
and returns a different message for each possible type.
Example - Literal Value In Match Patterns
A literal value is a value that is written directly into the code. It is a value that is not calculated and is not the result of an expression or a function.
Literal values can be strings, integers, floating-point numbers, and boolean values.
A literal value in match patterns is a value that is exactly what it appears to be.
For example, in the code below, 'James'
is a literal value.
We will start by writing an if...elif...else
version of the same application.
The check_common_name
function takes a single argument name
and uses an if
statement with multiple elif
clauses to check if name
is one of five specific names.
If name
is any of these names, the function returns True
. If name
is not one of these names, the function returns False
.
if...elif...else
version:
# Devoriales.com, 2022
# Path: switch_case/example_1.py
# checks if a name is common or not. Uses if else statements
def check_common_name(name):
# switch case
if name == 'John':
return True
elif name == 'Mary':
return True
elif name == 'James':
return True
elif name == 'Sarah':
return True
elif name == 'Robert':
return True
else:
return False
print(check_common_name('John')) # checks if a name is common or not Output: True
print(check_common_name('Alex')) # checks if a name is common or not Output: False
Output:
True
False
The next step is to write the same logic, but this time, we will use pattern matching:
# Devoriales.com, 2022
# Path: pattern_matching/example_1.py
# Description: Checking if a name is common using Structural Pattern Matching
def check_common_name(name):
# switch case
match name:
case 'John':
return True
case 'Mary':
return True
case 'James':
return True
case 'Sarah':
return True
case 'Robert':
return True
case _: # default case which is the same as else
return False
print(check_common_name('John')) # checks if a name is common or not
print(check_common_name('Aleks')) # checks if a name is common or not
When we run this code, we get the following output, just like the previous code with if...elif...else
True
False
The check_common_name
function takes a single argument name
and uses a switch statement implemented with the match
and case
keywords to check if name
is one of five specific names.
If name
is any of these names, the function returns True
. If name
is not one of these names, the function returns False
.
Example - Movie Menu app
Assume that we are running a movie-streaming business and we want our users to select a movie from a menu.
The movie menu app is a simple command-line program that allows the user to choose a movie from a list of options.
The function that we will write takes a single argument movie
, which is the user's selection.
We will start by wrting an version that is based on if...elif...else
statements.
Code: menu data is kept in a dictionary
# movie dictionary
movies = {
'1': 'The Matrix',
'2': 'The Avengers',
'3': 'The Dark Knight',
'4': 'The Godfather',
}
print('Movie Menu', end='\n****************\n')
for key, value in movies.items():
print(f'{key}: {value}')
Now, let's create a function that will take input as an argument. The argument will be the actual movie key-value from a dictionary.
Next, it will perform some if...elif...else
statements to check what selection the user has chosen:
# movie menu with if else statements
def movie_menu(movie):
# if else statements
if movie == '1':
return 'Loading The Matrix'
elif movie == '2':
return 'Loading The Avengers'
elif movie == '3':
return 'Loading The Dark Knight'
elif movie == '4':
return 'Loading The Godfather'
else:
return 'Loading Invalid Choice'
# movie dictionary
movies = {
'1': 'The Matrix',
'2': 'The Avengers',
'3': 'The Dark Knight',
'4': 'The Godfather',
}
print('Movie Menu', end='\n****************\n')
for key, value in movies.items():
print(f'{key}: {value}')
input = input('Select movie to watch: ')
print(movie_menu(input))
The menu will be presented like this:
Movie Menu
****************
1: The Matrix
2: The Avengers
3: The Dark Knight
4: The Godfather
Select movie to watch: 2
Loading The Avengers
Next, we will turn this logic into structural pattern matching and notice how cleaner it becomes.
# devoriales.com, 2022
# Movie Menu
# Path: pattern_matching/example_2.py
# Description: Movie Menu using Structural Pattern Matching
# movie menu with match statement
def movie_menu(movie):
# switch case
match movie:
case '1':
return 'Loading The Matrix'
case '2':
return 'Loading The Avengers'
case '3':
return 'Loading The Dark Knight'
case '4':
return 'Loading The Godfather'
case _:
return 'Loading Invalid Choice'
# movie dictionary
movies = {
'1': 'The Matrix',
'2': 'The Avengers',
'3': 'The Dark Knight',
'4': 'The Godfather',
}
print('Movie Menu', end='\n****************\n')
for key, value in movies.items():
print(f'{key}: {value}')
input = input('Select movie to watch: ')
print(movie_menu(input))
The menu in the terminal looks the same for both versions:
Movie Menu
****************
1: The Matrix
2: The Avengers
3: The Dark Knight
4: The Godfather
Select movie to watch: 3
Loading The Dark Knight
- The
movie_menu()
function uses a switch statement implemented with thematch
andcase
keywords to check the value ofmovie
and return the appropriate response. - The
case
clauses specify the different values thatmovie
can take, and the default_
case is used as a catch-all or default case, similar to theelse
clause in anif
statement. - The application defines a dictionary called
movies
containing a list of movie options. - The application prints out the movie menu by iterating over the dictionary and printing the key-value pairs.
- The user is prompted to select a movie by entering a number between 1 and 4.
- The user's input is passed as the argument to the
movie_menu
function, which checks the value ofmovie
and returns a string indicating which movie is being loaded. - If the user's input does not match any of the
case
clauses, the default_
case is matched and the function returns a string indicating that the choice is invalid.
❗The last wildcard matching pattern is defined as an underscore _
You can name it as you like, but in Python, it is a good convention to write it as '_' as the last evaluation. You don't want other Python developers to hate you 🤣
Example - Movie Streaming class
In this example, we will define a Movie
class that has three instance variables: name
, year
, and rating
.
The class has a class method get_movie
, which takes an integer option
as input and returns a Movie
instance based on the value of option
.
The code defines a Movie
class that has three instance variables: name
, year
, and rating
. It also has a class method get_movie
, which takes an integer option
as input and returns a Movie
instance based on the value of option
.
The get_movie
class method uses structural pattern matching to create a Movie
instance based on the value of option
.
For example, If option
is 1, it returns a Movie
instance with the name 'The Matrix', year 1999, and rating 8.7. If option
is 2, it returns a Movie
instance with the name 'The Avengers' and so on.
If option
is any other value, it returns a Movie
instance with the name 'Invalid Choice', year 0, and rating 0. The __str__
method of the Movie
class returns a string representation of the Movie
instance with the name
, year
, and rating
variables.
❗In this example, we have used the Object-Oriented Programming style where we have created a class instance. Not only that, we have used something called an alternative constructor from the class method. If you want to learn Object Oriented Programming in Python, read the following OOP article series here
Based on our selection in the menu, we will instantiate a class instance and send in the required arguments that are taken as parameters by our Movie class:
# devoriales.com, 2022
# Movie Streaming Service using Structural Pattern Matching
# Path: pattern_matching/example_3.py
# Description: Movie Menu using Structural Pattern Matching and Classes
class Movie():
def __init__(self, name, year, rating):
self.name = name
self.year = year
self.rating = rating
@classmethod
def get_movie(cls, option: int):
match option:
case 1:
return cls('The Matrix', 1999, 8.7)
case 2:
return cls('The Avengers', 2012, 8.1)
case 3:
return cls('The Dark Knight', 2008, 9.0)
case 4:
return cls('The Godfather', 1972, 9.2)
case _:
return cls('Invalid Choice', 0, 0)
def __str__(self):
return f'Name: {self.name} Year: {self.year} - Rating: {self.rating}'
# movie class instance
movies = {
'1': 'The Matrix',
'2': 'The Avengers',
'3': 'The Dark Knight',
'4': 'The Godfather',
}
if __name__ == '__main__':
# Presenting a movie menu
print('Movie Menu', end='\n****************\n')
for key, value in movies.items():
print(f'{key}: {value}')
# Get user input
input = input('Select movie to watch: ')
# Create a movie class instance from the alternative constructor
movie = Movie.get_movie(int(input))
print(movie) # print movie details from our class instance
The code also defines a dictionary movies
with keys '1', '2', '3', and '4' and corresponding values '
It presents a movie menu to the user with a list of movies from the movies
dictionary and prompts the user to select a movie to watch. The user input is taken as an integer and passed to the get_movie
method to create a Movie
instance. The details of the Movie
instance is then printed to the screen.
Output:
Movie Menu
****************
1: The Matrix
2: The Avengers
3: The Dark Knight
4: The Godfather
Select movie to watch: 2
Name: The Avengers Year: 2012 - Rating: 8.1
Example - HTTP status codes matching
In the following example, we will write a simple function that accepts an HTTP status code and returns an output. By now, we have seen several if...elif...else
equivalent examples, this time, we will only write the Switch-Case statement:
# devoriales.com, 2022
# Path : pattern_matching/example_4.py
# check http status codes using match statement
# switch case
def status_code(status_code):
# switch case
match status_code:
case 200:
return 'OK'
case 404:
return 'Not Found'
case 500:
return 'Internal Server Error'
case _:
return 'Unknown'
print(status_code(500)) # check status code
Output:
Internal Server Error
The function status_code
is a simple implementation of a switch statement that takes an integer as input and returns a string based on the value of the integer.
For example, if the value of status_code
is 200, the function returns 'OK'.
Example - Switch Case with OR condition
Sometimes we need to write OR condition statements like the following ( in the following example, we're writing an if...elif...else
statement :
>>> if x or y:
... return True
This is also achievable with pattern matching.
# devoriales.com, 2022
# Path: pattern_matching/example_5.py
# description: benchmarking switch case vs if else in python
# match statement
def sportcars(car):
print(car)
# switch case
match car:
case 'Ferrari' | 'Lamborghini' | 'Porsche' | 'Aston Martin': # multiple cases with OR condition
return 'Sportscar'
case _:
return 'Not a sportscar'
# def sportcars(car):
# if car == 'Ferrari' or car == 'Lamborghini' or car == 'Porsche' or car == 'Aston Martin':
# return 'Sportscar'
# else:
# return 'Not a sportscar'
print(sportcars('Ferrari')) # multiple cases with OR condition will return Sportscar
print(sportcars('Fiat')) # multiple cases with OR condition will return Not a sportscar
Output:
Ferrari
Sportscar
Fiat
Not a sportscar
The code above is defining a function called sportcars
that takes in a parameter car
. The function has a match
statement that is used to perform pattern matching on the value of car
.
- The
match
statement has a number ofcase
clauses that are used to check if the value ofcar
matches the specified pattern. The firstcase
clause checks ifcar
is equal to'Ferrari'
,'Lamborghini'
,'Porsche'
, or'Aston Martin'
. If any of these conditions are met, the function returns the string'Sportscar'
. - The
|
(pipe) symbol is being used to specify multiple cases in a single line in a match statement. The|
operator is used to represent the logical OR operator in Python. It is used to check if either of the conditions on either side of the|
operator is true. - In this specific example, the
|
operator is used to specify multiple cases in the match statement that are separated by|
. If any of these cases match the value of thecar
variable, the'Sportscar'
string will be returned. If none of the cases match the value ofcar
, the'Not a sportscar'
string will be returned. - The second
case
clause is a wildcard pattern, indicated by the_
symbol, which is used to catch any value ofcar
that does not match any of the previouscase
clauses. If the value ofcar
does not match any of the previouscase
clauses, the function returns the string'Not a sportscar'
.
Example - Switch Case with AND condition
The following example is similar to the previous one, except this time we will write a condition that has to match two arguments.
Withif...elif...else
statement, it would look like the following:
# devoriales.com, 2022
# Path: pattern_matching/example_6.py
# description: multiple cases with AND condition
# if else
def sportcars(*car):
if car[0] == 'Ferrari' and car[1] == 'Lamborghini':
return 'Sportscars'
else:
return 'Not a sportscar'
print(sportcars('Ferrari', 'Lamborghini')) # multiple cases with AND condition will return Sportscars
print(sportcars('Fiat', 'Lamborghini')) # multiple cases with AND condition will return Not a sportscar
Now, we will do similar with pattern matching:
# devoriales.com, 2022
# Path: pattern_matching/example_6.py
# description: multiple cases with AND condition
# with AND condition
def sportcars(*car):
print(car)
# switch case
match car:
case 'Ferrari', 'Lamborghini': # multiple cases with AND condition
return 'Sportscars'
case _:
return 'Not sportscars'
print(sportcars('Ferrari', 'Lamborghini')) # Returns "Sportscars"
print(sportcars('Fiat', 'Lamborghini')) # Returns "Not sportscars"
print(sportcars('Ferrari', 'Fiat')) # Returns "Not sportscars"
print(sportcars('Ferrari', 'Lamborghini', 'Porsche')) # Returns "Not sportscars"
Output:
Sportscars
Not a sportscar
Not a sportscar
Sportscars
- The
sportcars()
function is defined to take a different number of arguments, which are passed as a tuple to the function as thecar
parameter. Thematch
statement is then used to perform structural pattern matching on thecar
argument, and different code is executed based on the structure of the argument. - With pattern matching, the asterisk (
*
) symbol is not being used to unpack the tuple of arguments passed to thesportcars
function. Instead, the tuple is being passed as a single argument to the function and is being matched using structural pattern matching. - The
AND
condition in this code refers to the fact that both elements of the tuple must match the specified patterns in order for the case to be considered a match. In this case, the pattern is'Ferrari', 'Lamborghini'
, so the value ofcar
must be a tuple with 'Ferrari' as the first element and 'Lamborghini' as the second element in order for the case to be considered a match. - The first case in the
match
statement checks for a tuple with two elements, both of which are equal to the strings"Ferrari"
and"Lamborghini"
, respectively. If both conditions are true, the function returns the string"Sportscars"
. - The last case is a default case that matches any other structure of the
car
tuple and returns the string"Not sportscars"
.
Example - More complex pattern matching
In this example, we will create an application that manages files and directories. We will use the os
library, which provides the ability to run real system commands.
The following code defines a function that executes different actions based on the content of a command
string, using structural pattern matching to match the structure of the string and take different actions based on the result:
# devoriales.com, 2022
# Path: pattern_matching/manage_files/file_handler.py
# Description: Handling files using switch case and match statement
'''
command is the parameter of the commander function
*file gets unpacked and is used as a parameter for the os.system function
'''
import os
command = input("Enter a command and a file: ")
def commander(command):
match command.split():
case ["help" | "h" | "?", *file ] if "--ask" in command: # if --ask is in the command then print the help message
print("Commands: cat, rm, ls, mv, cp, touch, mkdir, rmdir, pwd, cd")
case ["cat", *file]: # if cat is in the command then print the file
files = [print(os.system(f"cat {x}")) for x in file] # prints the file if it exists by using list comprehension
case ["rm" | "remove" | "delete", *file]:
print(os.system(f"rm {file}"))
case ["ls" | "list", *file]:
print(os.system(f"ls {file}"))
case ["mv" | "move", *file]:
print(os.system(f"mv {file}"))
case ["cp" | "copy", *file]:
print(os.system(f"cp {file}"))
case ["touch", *file]:
for x in file:
print(os.system(f"touch {x}"))
case ["mkdir" | "make directory", *file]:
print(os.system(f"mkdir {file}"))
case ["rmdir" | "remove directory", *file]:
print(os.system(f"rmdir {file}"))
case ["pwd" | "print working directory", *file]:
print(os.system(f"pwd {file}"))
case ["cd" | "change directory", *file]:
print(os.system(f"cd {file}"))
case _:
print("Command not found")
commander(command)
The code defines a function called commander
that takes a single argument command
and executes different actions based on the content of the command
string.
The commander
function uses structural pattern matching to match the structure of the command
string, which is split into a list of words using the split
method. Each case in the match
statement checks for a specific sequence of words in the command
list and performs a different action based on the result.
-
The asterisk (
*
) symbol is used in thematch
statement to match a variable number of elements in thecommand.split()
list. The first case in thematch
statement checks for the words"help"
,"h"
, or"?"
followed by any number of additional words, and only matches if the string"--ask"
is present in the originalcommand
string. If this case is matched, the function prints a list of available commands. - The second case checks for the word
"cat"
followed by any number of additional words and executes acat
command on each of the additional words using a list comprehension. - The third case checks for the words
"rm"
,"remove"
, or"delete"
followed by any number of additional words, and executes arm
command on the additional words. And so on... - The default case "
_
" matches any other structure of thecommand
list and prints a message indicating that the command was not found.
Examples of what you can do in the terminal:
python file_handler.py
Enter a command and a file: help --ask
Commands: cat, rm, ls, mv, cp, touch, mkdir, rmdir, pwd, cd
Enter a command and a file: cat log1 log2
yyyy/mm/dd hh:mm:ss.sss pid tid message-id message(LANG=en)
0046 2003/12/06 19:51:32.250 my_app1 00EB7859 012A54F9 AP1_10000-I application1 start.
0048 2003/12/06 19:51:32.265 my_app1 00EB7859 012A54F9 AP1_10100-E Catch an exception!
0049 2003/12/06 19:51:32.265 my_app1 00EB7859 012A54F9 AP1_10001-I application1 end.
0
yyyy/mm/dd hh:mm:ss.sss pid tid message-id message(LANG=en)
0046 2003/12/06 19:51:32.250 my_app1 00EB7859 012A54F9 AP1_10000-I application1 start.
0048 2003/12/06 19:51:32.265 my_app1 00EB7859 012A54F9 AP1_10100-E Catch an exception!
0049 2003/12/06 19:51:32.265 my_app1 00EB7859 012A54F9 AP1_10001-I application1 end.
Example - Data Deconstruction
Data deconstruction refers to the process of extracting individual elements from data structures such as lists, tuples, and dictionaries, and assigning them to separate variables.
consider the following example:
my_tuple = ('Woman', 'singing')
obj, verb = my_tuple
print(obj) # 'woman'
print(verb) # 'singing'
In this example, the variables obj
and verb
are assigned the values of the first and second elements of the tuple, respectively. This allows you to access and manipulate the individual elements of the tuple separately.
Data deconstruction can be a convenient way to extract and work with specific elements of a data structure and can be particularly useful when combined with structural pattern matching, as seen in some examples before.
Let's have a look at an example with if...elif..else statement.
The following function takes a single argument x
and uses an if
statement to check if the value of x
is a tuple.
If True, the function returns a string that includes the first, second, and third elements of the tuple. If the value of x
is not a tuple, the function returns a string indicating that x
is not a tuple.
'''
devoriales.com, 2022
Path: pattern_matching/data_deconstruction.py
description: data deconstruction - tuple unpacking
'''
# data deconstruction with if else
def data_deconstruction(x):
if type(x) == tuple:
return f"{x[0]} is working as a {x[1]} at {x[2]}"
else:
return f"{x} is not a tuple"
print(data_deconstruction(('Oliver', 'developer', 'Google')))
Now we can make the same logic as in the previous version, but this time with match pattern.
The data_deconstruction
function takes a single argument x
and uses structural pattern matching to deconstruct the value of x
into three separate variables: name
, job
, and company
.
The match
statement uses a tuple pattern to match a tuple with three elements, and assigns the first element to the name
variable, the second element to the job
variable, and the third element to the company
variable. If the value of x
is a tuple with three elements, the function returns a string that includes the values of the name
, job
, and company
variables.
If the value of x
is not a tuple with three elements, the function uses the default case (indicated by the _
wildcard) to return a string indicating that x
is not a tuple.
'''
devoriales.com, 2022
Path: pattern_matching/data_deconstruction.py
description: data deconstruction - tuple unpacking
'''
# data deconstruction with match case
def data_deconstruction(x):
match x:
case (name, job, company):
return f"{name} is working as a {job} at {company}"
case _:
return f"{x} is not a tuple"
print(data_deconstruction(('Sanja', 'designer', 'Apple')))
Output:
Sanja is working as a designer at Apple
Benchmark Switch Case Statement vs If...Else statement
To compare the if...elif...else statement against the match statement, we will write code that compares the performance of two functions: check_common_name()
, which uses the match pattern, and check_with_if()
, which uses the if...elif...else
statement.
To do this, we will use the following process:
- We have a pre-generated data file containing 60,000 names.
- A Python list is populated with the names using a context manager.
- One additional name is added to the list.
- We have written two functions: one using a switch case statement and another using an if...else statement.
- We will send an argument with a specific name that is written at the end of the list to each function.
- We will time each function separately and compare the results. The quicker function will be declared the winner.
Code:
# devoriales.com, 2022
# Path: pattern_matching/benchmarking.py
# description: benchmarking switch case vs if else in python
import time
# create a list of names
my_name_list = []
# context manager to open the file and read the names and append them to the list
with open ('data', 'r') as f:
for line in f:
my_name_list.append(line)
# add a name to the list to the very end
my_name_list.append('Moby Dick')
# check if name is in list with switch case
def check_common_name(name):
# switch case
for x in my_name_list:
match name:
case str(x):
return True
case _:
return False
# check if name is in list with if else
def check_with_if(name):
# if else
for x in range(len(my_name_list)):
if name == my_name_list[x].splitlines()[0]:
return True
return False
# time the functions
start_if = time.time() # time the function with if else
print(check_with_if('Moby Dick'))
end_if = time.time()
print(end_if - start_if)
result_if = end_if - start_if # get the result of the function with if else
start_switch = time.time() # time the function with switch case
print(check_common_name('Moby Dick'))
end_switch = time.time()
print(end_switch - start_switch)
result_switch = end_switch - start_switch # get the result of the function with switch case
#compare the results
if result_if > result_switch:
print('switch case is faster')
else:
print('if else is faster')
Output:
True
0.04650712013244629
True
1.4781951904296875e-05
switch statement is faster
As we can see, the switch statement is faster. This is a very simple benchmark, and it probably takes a much larger data set to come to a fair conclusion.
This is what the code is doing:
- Defines two functions,
check_common_name()
andcheck_with_if()
, that both check whether a givenname
is present in a list of names calledmy_name_list
. Themy_name_list
is initialized by reading a file calleddata
and add each line to the list, and then append the string"Moby Dick"
to the end of the list. - The
check_common_name()
function uses afor
loop to iterate through each element in themy_name_list
and performs structural pattern matching on thename
argument using thematch
statement.- The first case in the
match
statement checks if thename
argument is equal to the current element in thefor
loop, and returnsTrue
if the condition is true. - The second case is a default case that returns
False
for any other value of thename
argument.
- The first case in the
- The
check_with_if()
function also uses afor
loop to iterate through each element in themy_name_list
, but instead of using structural pattern matching, it uses anif
statement to check if thename
argument is equal to the current element in thefor
loop.- If the condition is true, the function returns
True
. If thefor
loop completes without finding a match, the function returnsFalse
.
- If the condition is true, the function returns
- After defining the two functions, the code measures the time it takes to execute each function by calling the
time
module'stime
function before and after the function call.- The code measures the time before and after calling each function and calculates the difference to determine the execution.
Summary
In this article, you have learned what structural pattern matching in Python is and how it compares to if...elif...else
statements.
We have seen several simpler use cases and examples and learned how to match single arguments and multiple arguments and use AND / OR conditions in our matching patterns.
Structural pattern matching does not necessarily require significantly less code compared to if...elif...else
statements, but there is a slightly smaller amount of code you need to write when writing match cases compared to if...elif...else
.
Structural pattern matching is also slightly more performant and faster according to the benchmarking we have done in this article, which could be another reason to start using it in future projects.
In my opinion, the biggest reason for using a switch case over if...elif...else
statements is that it provides much clearer code readability.
About the Author
Aleksandro Matejic, a Cloud Architect, began working in the IT industry over 21 years ago as a technical specialist, right after his studies. Since then, he has worked in various companies and industries in various system engineer and IT architect roles. He currently works on designing Cloud solutions, Kubernetes, software development, and DevOps technologies.
In his spare time, Aleksandro works on different development projects such as developing devoriales.com, a blog and learning platform launching in 2022/2023. In addition, he likes to read and write technical articles about software development and DevOps methods and tools. You can contact Aleksandro by visiting his LinkedIn Profile.