Python 3.10 Structural Pattern Matching

In this post we will install the beta version of Python 3.10 and use the soon to be release Python Structural Pattern Matching. This is very similar to switch statements in C++. Microsoft outlines the C++ switch statement here: (LINK). This is a very exciting new feature of Python!

Demonstrating PEP 634: Structural Pattern Matching

Structural pattern matching has been added in the form of a match statement and case statements of patterns with associated actions. Patterns consist of sequences, mappings, primitive data types as well as class instances. Pattern matching enables programs to extract information from complex data types, branch on the structure of data, and apply specific actions based on different forms of data.

At the time of writing this post Python 3.9 is the latest version of Python and Python 3.10 is in beta mode. To be able to experiment with Python 3.10 we have to install the beta version, which requires a more elaborate approach than a normal Python installation.

How to Install a Beta Version of Python

Python 3.10.0b2 - Released May 31, 2021

Overview of Procedure

  1. Ubuntu Server 20.04 LTS
  2. Install required libraries
$ sudo apt-get install libssl-dev openssl make gcc
  1. Copy url for specific Python version desired

https://www.python.org/ftp/python/3.10.0/Python-3.10.0b2.tgz

  1. Go to /opt directory and download Python to here
$ cd /opt
$ wget https://www.python.org/ftp/python/3.10.0/Python-3.10.0b2.tgz
  1. Extract the archive
$ tar xzvf Python-3.10.0b2.tgz
  1. Drop into the new Python directory
$ cd Python-3.10.0b2
  1. Compile the new version - each of these take awhile
$ ./Configure
$ make
$ sudo install
  1. Make this version of Python usable anywhere
sudo ln -fs /opt/Python-3.10.0b2/Python /usr/bin/python3.10
  1. Check your Python Version now
$ python3 --version

success looks like:

Python 3.10.0b2

Using Match in Python - Examples

Here we run through some examples outlined in PEP 634 but I try to include the full code so that one can run the examples themselves.

Basic Use Case

Here we will create a function that gets a http error status code and uses the match to return the appropriate response.

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the Internet"


def main():
    statuses = [400,400,404,400,418,404,418,'']
    for status in statuses:
        print(http_error(status))


if __name__ == "__main__":
    main()

For the above code the statuses list of integers and a string is looped through and each one sent to the http_error() function where the response is returned to main() and print to the terminal. The outputs are shown below.

OUTPUT:

Bad request
Bad request
Not found
Bad request
I'm a teapot
Not found
I'm a teapot
Something's wrong with the Internet

Note the last block: the variable name _ acts as a wildcard and insures the subject will always match. The use of _ is optional.

No Wildcard

Rerunning the code above but without the _ wildcard.

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"


def main():
    statuses = [400,400,404,400,418,404,418,'']
    for status in statuses:
        print(http_error(status))


if __name__ == "__main__":
    main()

For the code above without the wild card the output is below. Take note of the last line. None where before it was Something's wrong with the Internet.

OUTPUT:

Bad request
Bad request
Not found
Bad request
I'm a teapot
Not found
I'm a teapot
None

A handy approach to remember depending on what you are trying to code.

Guard

We can add an if clause to a pattern, known as a “guard”. If the guard is false, match goes on to try the next case block. Note that value capture happens before the guard is evaluated:

def use_a_guard(x,y):
    match x,y:
        case x, y if x == y:
            print(f"The point is located on the diagonal Y=X at {x}.")
        case x, y:
            print(f"Point is not on the diagonal.")


def main():
    # USING GUARD
    points = [[1,1], [1,2], [3,3]]
    for point in points:
        x = point[0]
        y = point[1]
        print(use_a_guard(x,y))


if __name__ == "__main__":
    main()

The main() function loops through the list points which contains two element lists which represent x,y points. The use_a_guard function checks to see if the points are on the diagonal, which means they equal each other. If x and y are the same values it returns a string stating such. This uses an if statement. If x and y are not equal the if statement is false and the match defaults to case x, y. The outputs can be seen below.

The point is located on the diagonal Y=X at 1.
None
Point is not on the diagonal.
None
The point is located on the diagonal Y=X at 3.
None

Conclusions

A switch statement is usually more efficient than a set of nested ifs. Deciding whether to use if-then-else statements or a switch statement is based on readability and the expression that the statement is testing. The Python Structural Pattern Matching offers advanced features that allow classes, guards and more which make it even more useful.

References