Flow control: match case

The goal

There is a new flow control in town. A switch case replacement, called match case.

Questions to David Rotermund

The match statement

The match statement is used for pattern matching. Syntax:

match_stmt   ::=  'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
subject_expr ::=  star_named_expression "," star_named_expressions?
                  | named_expression
case_block   ::=  'case' patterns [guard] ":" block

Normal “switch” case

for i in range(0, 4):

    match (i):
        case 0:
            print("This is a 0")

        case 0:
            print("This is a 0 too.")

        case 1:
            print("This is a 1.")

        case 2:
            print("This is a 2.")

        case _:
            print(f"I don't know what to do with a {i}!")

Output:

This is a 0
This is a 1.
This is a 2.
I don't know what to do with a 3!

_ is the default. And after every case state has an in-build break; (If you don’t know what this means: A. You are not a C/C++ programmer. B. You can ignore this information. :-) ).

Unpacking parameters 

This is an optional topic!

Different from a normal switch we can deal with several input parameters at the same time and even use wildcards:

def what_to_do(input):
    match (input):
        case (0, 0):
            print("This is (0,0) - A")

        case (0, 1):
            print("This is (0,1) - B")

        case (x, 0):
            print(f"This is a {x} - C")

        case (x, y):
            print(f"This is a {x} and {y} - D")

        case _:
            print(f"I don't know what to do with {input}- E!")
    print("")


second_parameter: int = 0
for i in range(0, 2):
    input = (i, second_parameter)
    print(f"Input: {input}")
    what_to_do(input)

second_parameter = 1
for i in range(0, 2):
    input = (i, second_parameter)
    print(f"Input: {input}")
    what_to_do(input)

input = "Hello!"
print(f"Input: {input}")
what_to_do(input)

Output:

Input: (0, 0)
This is (0,0) - A

Input: (1, 0)
This is a 1 - C

Input: (0, 1)
This is (0,1) - B

Input: (1, 1)
This is a 1 and 1 - D

Input: Hello!
I don't know what to do with Hello!- E!

OR

This is an optional topic!

We can combine patter by (or):
def what_to_do(input):
    match (input):
        case (0, 0) | (0, 1):
            print("This is (0,0) or (0,1) - A")

        case (x, 0):
            print(f"This is a {x} - C")

        case (x, y):
            print(f"This is a {x} and {y} - D")

        case _:
            print(f"I don't know what to do with {input}- E!")
    print("")


second_parameter: int = 0
for i in range(0, 2):
    input = (i, second_parameter)
    print(f"Input: {input}")
    what_to_do(input)

second_parameter = 1
for i in range(0, 2):
    input = (i, second_parameter)
    print(f"Input: {input}")
    what_to_do(input)

Output:

Input: (0, 0)
This is (0,0) or (0,1) - A

Input: (1, 0)
This is a 1 - C

Input: (0, 1)
This is (0,0) or (0,1) - A

Input: (1, 1)
This is a 1 and 1 - D

You can also use the or on parts of the pattern (output is the same as before):

def what_to_do(input):
    match (input):
        case (0, (0 | 1)):
            print("This is (0,0) or (0,1) - A")

        case (x, 0):
            print(f"This is a {x} - C")

        case (x, y):
            print(f"This is a {x} and {y} - D")

        case _:
            print(f"I don't know what to do with {input}- E!")
    print("")


second_parameter: int = 0
for i in range(0, 2):
    input = (i, second_parameter)
    print(f"Input: {input}")
    what_to_do(input)

second_parameter = 1
for i in range(0, 2):
    input = (i, second_parameter)
    print(f"Input: {input}")
    what_to_do(input)

If guards

This is an optional topic!

We can distinguish between cases by using if guard:

def what_to_do(input):
    match (input):
        case (x, y) if x == y:
            print(f"Both are {x} - A ")
        case (x, y):
            print(f"This is a {x} and {y} - B ")

    print("")


second_parameter: int = 0
for i in range(0, 2):
    input = (i, second_parameter)
    print(f"Input: {input}")
    what_to_do(input)

second_parameter = 1
for i in range(0, 2):
    input = (i, second_parameter)
    print(f"Input: {input}")
    what_to_do(input)

Output:

Input: (0, 0)
Both are 0 - A 

Input: (1, 0)
This is a 1 and 0 - B 

Input: (0, 1)
This is a 0 and 1 - B 

Input: (1, 1)
Both are 1 - A 

Data classes

This is an optional topic!

We can combine it nicely with data classes:

from dataclasses import dataclass


@dataclass
class SomeDataStructure:
    x: int
    y: int


def what_to_do(input):
    print(input)
    match (input):
        case SomeDataStructure(x=0, y=0):
            print("case A")
        case SomeDataStructure(x=1, y=0):
            print("case B")
        case SomeDataStructure():
            print("case C")
        case _:
            print("What?")

    print("")


data_0_0 = SomeDataStructure(0, 0)
what_to_do(data_0_0)
data_0_1 = SomeDataStructure(0, 1)
what_to_do(data_0_1)
data_1_0 = SomeDataStructure(1, 0)
what_to_do(data_1_0)
data_1_1 = SomeDataStructure(1, 1)
what_to_do(data_1_1)
data_broken = "I am broken!"
what_to_do(data_broken)

Output:

SomeDataStructure(x=0, y=0)
case A

SomeDataStructure(x=0, y=1)
case C

SomeDataStructure(x=1, y=0)
case B

SomeDataStructure(x=1, y=1)
case C

I am broken!
What?

As

This is an optional topic!

We can use as to name a variable in a case clause (if you have a sequence i.e. tuple or list of elements then you can use as on the individual elements): 

from dataclasses import dataclass


@dataclass
class SomeDataStructure:
    x: int
    y: int


def what_to_do(input):
    print(input)
    match (input):
        case SomeDataStructure(x=0, y=0):
            print("case A")
        case SomeDataStructure(x=1, y=0):
            print("case B")
        case SomeDataStructure() as someinput:
            print(f"case C : {someinput.x} {someinput.y}")
        case _:
            print("What?")

    print("")


data_0_0 = SomeDataStructure(0, 0)
what_to_do(data_0_0)
data_0_1 = SomeDataStructure(0, 1)
what_to_do(data_0_1)
data_1_0 = SomeDataStructure(1, 0)
what_to_do(data_1_0)
data_1_1 = SomeDataStructure(1, 1)
what_to_do(data_1_1)
data_broken = "I am broken!"
what_to_do(data_broken)

Output:

SomeDataStructure(x=0, y=0)
case A

SomeDataStructure(x=0, y=1)
case C : 0 1

SomeDataStructure(x=1, y=0)
case B

SomeDataStructure(x=1, y=1)
case C : 1 1

I am broken!
What?

List

This is an optional topic!

This is how we can deal with lists that have different length:

def what_to_do(input):
    print(input)
    match (input):
        case []:
            print("case A")
        case [0]:
            print("case B")
        case [x]:
            print(f"case C {x}")
        case [0, 0]:
            print("case D")
        case _:
            print("What?")

    print("")


what_to_do([])
what_to_do([0])
what_to_do([1])
what_to_do([0, 0])
what_to_do([0, 1])
what_to_do("Hello")

Output:

[]
case A

[0]
case B

[1]
case C 1

[0, 0]
case D

[0, 1]
What?

Hello
What?

Dictionaries

This is an optional topic!

The whole match apparatus works also with dictionaries. You can even distinguish between what kind of variable is stored behind a key (e.g. int vs str): 

def what_to_do(input):
    print(input)
    match (input):
        case {"x": str() as x}:
            print(f"{x} - str")
        case {"x": int() as x}:
            print(f"{x} - int")
        case {"y": value}:
            print(f"{value} - A")
        case {"a": value_a, "b": value_b}:
            print(f"a={value_a} b={value_b} - B")
    print("")


dictionary_a = {"x": str(1)}
what_to_do(dictionary_a)
dictionary_b = {"x": int(1)}
what_to_do(dictionary_b)
dictionary_c = {"x": int(1), "y": 5}
what_to_do(dictionary_c)
dictionary_d = {"y": 5}
what_to_do(dictionary_d)
dictionary_e = {"a": 6, "b": 7}
what_to_do(dictionary_e)
dictionary_f = {"b": 8, "a": 10}
what_to_do(dictionary_f)

Output:

{'x': '1'}
1 - str

{'x': 1}
1 - int

{'x': 1, 'y': 5}
1 - int

{'y': 5}
5 - A

{'a': 6, 'b': 7}
a=6 b=7 - B

{'b': 8, 'a': 10}
a=10 b=8 - B

Reference

The source code is Open Source and can be found on GitHub.