472 lines
8.3 KiB
Markdown
472 lines
8.3 KiB
Markdown
|
# Flow control: match case
|
|||
|
{:.no_toc}
|
|||
|
|
|||
|
<nav markdown="1" class="toc-class">
|
|||
|
* TOC
|
|||
|
{:toc}
|
|||
|
</nav>
|
|||
|
|
|||
|
## The goal
|
|||
|
There is a new flow control in town. A switch case replacement, called match case.
|
|||
|
|
|||
|
Questions to [David Rotermund](mailto:davrot@uni-bremen.de)
|
|||
|
|
|||
|
## The [match](https://docs.python.org/3/reference/compound_stmts.html#the-match-statement) statement
|
|||
|
|
|||
|
The match statement is used for pattern matching. Syntax:
|
|||
|
|
|||
|
```python
|
|||
|
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
|
|||
|
|
|||
|
```python
|
|||
|
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:
|
|||
|
|
|||
|
```python
|
|||
|
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
|
|||
|
|
|||
|
Different from a normal switch we can deal with several input parameters at the same time and even use wildcards:
|
|||
|
|
|||
|
```python
|
|||
|
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:
|
|||
|
|
|||
|
```python
|
|||
|
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
|
|||
|
|
|||
|
We can combine patter by | (or):
|
|||
|
|
|||
|
```python
|
|||
|
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:
|
|||
|
|
|||
|
```python
|
|||
|
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):
|
|||
|
|
|||
|
```python
|
|||
|
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
|
|||
|
We can distinguish between cases by using **if guard**:
|
|||
|
|
|||
|
```python
|
|||
|
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:
|
|||
|
|
|||
|
```python
|
|||
|
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
|
|||
|
We can combine it nicely with data classes:
|
|||
|
|
|||
|
```python
|
|||
|
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:
|
|||
|
|
|||
|
```python
|
|||
|
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
|
|||
|
|
|||
|
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):
|
|||
|
|
|||
|
```python
|
|||
|
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:
|
|||
|
|
|||
|
```python
|
|||
|
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 how we can deal with lists that have different length:
|
|||
|
|
|||
|
```python
|
|||
|
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:
|
|||
|
|
|||
|
```python
|
|||
|
[]
|
|||
|
case A
|
|||
|
|
|||
|
[0]
|
|||
|
case B
|
|||
|
|
|||
|
[1]
|
|||
|
case C 1
|
|||
|
|
|||
|
[0, 0]
|
|||
|
case D
|
|||
|
|
|||
|
[0, 1]
|
|||
|
What?
|
|||
|
|
|||
|
Hello
|
|||
|
What?
|
|||
|
```
|
|||
|
|
|||
|
## Dictionaries
|
|||
|
|
|||
|
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):
|
|||
|
|
|||
|
```python
|
|||
|
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:
|
|||
|
|
|||
|
```python
|
|||
|
{'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
|
|||
|
* [PEP 636 – Structural Pattern Matching: Tutorial](https://peps.python.org/pep-0636/)
|
|||
|
|