#pythonlearning #python #pythonprogramming #programming
Note: The original post can be found here
Thanks to rawpixel.com for the wonderful images
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.
It can be used as an alternative to usingif...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:
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 verbdef 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 tupleprint(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 calledcheck_type() that takes an object as an argument and returns a messagebased 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 elsedef 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.
Theif...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 thematch statement:
'''devoriales.comPath: pattern_matching/example_0.pydescription: type based dispatch using match case'''# type based using structural pattern matchingdef 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 integerprint(check_type(1.0)) # 1.0 is a floatprint(check_type([])) # [] is a listprint(check_type(())) # () is a tupleprint(check_type({})) # {} is a dictprint(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.
Thematch statement checks the type of x and returns a different message for each possible type.
Example - Literal Values 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.
Ifname 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 statementsdef 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 Falseprint(check_common_name('John')) # checks if a name is common or not Output: Trueprint(check_common_name('Alex')) # checks if a name is common or not Output: False
Output:
TrueFalse
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 Matchingdef 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 notprint(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:
TrueFalse
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 appis 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 argumentmovie, which is the user's selection.
We will start by writing an version that is based on if...elif...else statements.
Code: menu data is kept in a dictionary
# movie dictionarymovies = { '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 statementsdef 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 dictionarymovies = { '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 Matrix2: The Avengers3: The Dark Knight4: The GodfatherSelect movie to watch: 2Loading 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 statementdef 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 dictionarymovies = { '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 Matrix2: The Avengers3: The Dark Knight4: The GodfatherSelect movie to watch: 3Loading The Dark Knight
❗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 aMovie class that has three instance variables: name, year, and rating.
Recommended by LinkedIn
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.
Theget_movie class method uses structural pattern matching to create aMovie instance based on the value of option.
For example, Ifoption 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.
Ifoption 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 Classesclass 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 Matrix2: The Avengers3: The Dark Knight4: The GodfatherSelect movie to watch: 2Name: 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 casedef 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 ofstatus_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 statementdef 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'print(sportcars('Ferrari')) # multiple cases with OR condition will return Sportscarprint(sportcars('Fiat')) # multiple cases with OR condition will return Not a sportscar
Output:
FerrariSportscarFiatNot 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.
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.
With if...elif...elsestatement, it would look like the following:
# devoriales.com, 2022# Path: pattern_matching/example_6.py# description: multiple cases with AND condition# if elsedef 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 Sportscarsprint(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 conditiondef 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:
SportscarsNot a sportscarNot a sportscarSportscars
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 oscommand = 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 calledcommander 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 thecommand 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.
Examples of what you can do in the terminal:
python file_handler.pyEnter a command and a file: help --askCommands: cat, rm, ls, mv, cp, touch, mkdir, rmdir, pwd, cdEnter 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 deconstructionrefers 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_tupleprint(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, 2022Path: pattern_matching/data_deconstruction.pydescription: data deconstruction - tuple unpacking'''# data deconstruction with if elsedef 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, 2022Path: pattern_matching/data_deconstruction.pydescription: data deconstruction - tuple unpacking'''# data deconstruction with match casedef 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:
Code:
# devoriales.com, 2022# Path: pattern_matching/benchmarking.py# description: benchmarking switch case vs if else in pythonimport time# create a list of namesmy_name_list = []# context manager to open the file and read the names and append them to the listwith open ('data', 'r') as f: for line in f: my_name_list.append(line) # add a name to the list to the very endmy_name_list.append('Moby Dick')# check if name is in list with switch casedef 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 elsedef 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 functionsstart_if = time.time() # time the function with if elseprint(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 elsestart_switch = time.time() # time the function with switch caseprint(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:
True0.04650712013244629True1.4781951904296875e-05switch 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:
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 toif...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
AleksandroMatejic, a Cloud Architect, began working in the IT industry over21 years agoas 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 designingCloud solutions, Kubernetes, software development, and DevOps technologies.
In his spare time, Aleksandro works ondifferent development projectssuch as developingdevoriales.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 hisLinkedIn Profile.