Alternative To if...elif...else Statements - Structural Pattern Matching In Python

Alternative To if...elif...else Statements - Structural Pattern Matching In Python

#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 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.

No alt text provided for this image
Structural pattern matching

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 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.

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:

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 writing 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 the match and case keywords to check the value of movie and return the appropriate response.
  • The?case clauses specify the different values that movie can take, and the default _ case is used as a catch-all or default case, similar to the else clause in an if 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 of movie 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'



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 of case clauses that are used to check if the value of car matches the specified pattern. The first case clause checks if car 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 the car variable, the 'Sportscar' string will be returned. If none of the cases match the value of car, 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 of car that does not match any of the previous case clauses. If the value of car does not match any of the previous case 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.

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 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 the car parameter. The match statement is then used to perform structural pattern matching on the car 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 the sportcars 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 of car 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 the match statement to match a variable number of elements in the command.split() list.?The first case in the?match 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 original command 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 a cat 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 a rm command on the additional words. And so on...
  • The default case "_" matches any other structure of the command 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:

  1. We have a pre-generated data file containing 60,000 names.
  2. A Python list is populated with the names using a context manager.
  3. One additional name is added to the list.
  4. We have written two functions: one using a switch case statement and another using an if...else statement.
  5. We will send an argument with a specific name that is written at the end of the list to each function.
  6. 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() and check_with_if(), that both check whether a given name is present in a list of names called my_name_list. The my_name_list is initialised by reading a file called data 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 a for loop to iterate through each element in the my_name_list and performs structural pattern matching on the name argument using the match statement.
  • The first case in the?match statement checks if the name argument is equal to the current element in the for loop, and returns True if the condition is true.
  • The second case is a default case that returns?False for any other value of the name argument.
  • The check_with_if() function also uses a for loop to iterate through each element in the my_name_list, but instead of using structural pattern matching, it uses an if statement to check if the name argument is equal to the current element in the for loop.
  • If the condition is true, the function returns?True. If the for loop completes without finding a match, the function returns False.
  • After defining the two functions, the code measures the time it takes to execute each function by calling the time module's time 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.

要查看或添加评论,请登录

Aleksandro Matejic的更多文章

  • Kubernetes - RBAC And Admission Controllers

    Kubernetes - RBAC And Admission Controllers

    #kubernetes #kubernetescluster Note: The original post can be found here Kubernetes - RBAC And Admission Controllers…

    1 条评论
  • Battle Python 3.10 vs Python 3.11

    Battle Python 3.10 vs Python 3.11

    #pythonlearning #python #pythonprogramming #programming Note: The original post can be found here Thanks to…

社区洞察

其他会员也浏览了