From 3e713e3a010af3d93790dbeb21160e7fd0c0e41e Mon Sep 17 00:00:00 2001 From: David Rotermund <54365609+davrot@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:03:35 +0100 Subject: [PATCH] Create README.md Signed-off-by: David Rotermund <54365609+davrot@users.noreply.github.com> --- python_basics/match/README.md | 471 ++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 python_basics/match/README.md diff --git a/python_basics/match/README.md b/python_basics/match/README.md new file mode 100644 index 0000000..3a445f3 --- /dev/null +++ b/python_basics/match/README.md @@ -0,0 +1,471 @@ +# Flow control: match case +{:.no_toc} + + + +## 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/) +