<jupyter_start><jupyter_text>Python 3.10 — **Structural Pattern Matching**References:- [PEP 622 Structural Pattern Matching](https://www.python.org/dev/peps/pep-0622/)- [PEP 635 Structural Pattern Matching: Motivation and Rationale](https://www.python.org/dev/peps/pep-0635/)- [PEP 636 Structural Pattern Matching: Tutorial](https://www.python.org/dev/peps/pep-0636/)  We've always had it!*) since at least Python 1.6 ...with assignment<jupyter_code>text = "18:32:50"
#text = "18:32"  # 🔧

# ↘️
h, m, s = text.split(":")

print(f"{h} hours, {m} minutes and {s} seconds")<jupyter_output>18 hours, 32 minutes and 50 seconds<jupyter_text>...with "for"<jupyter_code>colors = {
    "red": (255, 0, 0),
    "blue": (0, 0, 255),
    "yellow": (255, 255, 0),
    #"transparent gray": (100, 100, 100, 0.5)  # 🔧
}

#   ↘️    ↘️
for name, (r, g, b) in colors.items():
    print(name, "is", f"#{r:02X}{g:02X}{b:02X}")<jupyter_output>red is #FF0000
blue is #0000FF
yellow is #FFFF00<jupyter_text>...with try/except<jupyter_code>try:
    open("non-existent.txt")
    #open("/etc/shadow")
except FileNotFoundError as e: # ⬅️
    print("No such file")
except PermissionError as e:   # ⬅️
    print(f"Not allowed (error {e.errno})")<jupyter_output>No such file<jupyter_text>Now we can use "match"<jupyter_code>text = "18:32:50"
#text = "18:32"     # 🔧
#text = "tea time"  # 🔧

match text.split(":"):
    case [h, m]:
        print(f"{h} hours and {m} minutes")
    case [h, m, s]:
        print(f"{h} hours, {m} minutes and {s} seconds")
    case _:
        print("oops")<jupyter_output>18 hours, 32 minutes and 50 seconds<jupyter_text>Let's have a look at 3 examples1️⃣ Event Queue2️⃣ JSON API3️⃣ Tree Traversal  Example 1: Event QueueSnippet from previous Big Python tutorial (event-driven Snake in PyGame, [link](https://youtu.be/_1KkTIxAZGg)).<jupyter_code>#                                                                                    Python 3.9
if isinstance(message, SnakeChangeDirectionMessage):
    if message.direction != opposite_direction:
        self.direction = message.direction
elif isinstance(message, EntityCollisionMessage) and message.entity is self:
    if isinstance(message.other, (Snake, Wall)):
        new_messages.append(GameOverMessage("Snake collision"))
    elif isinstance(message.other, Food):
        self.max_length += 1
        new_messages.append(RemoveEntityMessage(message.other))
        new_messages.append(SpawnFoodMessage())
#                                                                                    Python 3.10
match message:
    case SnakeChangeDirectionMessage(direction):
        if direction != opposite_direction:
            self.direction = direction
    case EntityCollisionMessage(entity, other) if entity is self:
        match other:
            case Wall() | Snake():
                new_messages.append(GameOverMessage("Snake collision"))
            case Food():
                self.max_length += 1
                new_messages.append(RemoveEntityMessage(other))
                new_messages.append(SpawnFoodMessage())<jupyter_output><empty_output><jupyter_text>Example 2: JSON APIWith mapping and string literal patterns, processing JSON dicts is easy.Let's look at commits in the Flask GitHub repository.<jupyter_code>import requests

#                       🔍 commits in Flask repository containing "jinja"
response = requests.get("https://api.github.com/search/commits?q=repo:pallets/flask+jinja",
                        headers={"Accept": "application/vnd.github.cloak-preview+json"})
data = response.json()
data
#                                                                                    Python 3.9
for item in data["items"]:
    sha = item["sha"]
    message = item["commit"]["message"]
    name = item["commit"]["author"]["name"]
#                                                                                    Python 3.10
for item in data["items"]:
    match item:
        case {"sha": sha, "commit": {"message": message, "author": {"name": name}}}:
            print(sha, "by", name)
            print(message)
            print(80*"-")
        #case {"sha": sha, "commit": {"message": message, "author": {"name": "<NAME>"}}}:<jupyter_output><empty_output><jupyter_text>Example 3: Tree TraversalFunctional programming with pattern matching and recursion can be very powerful for processing tree-like structures.This example shows derivation and simplification of mathematical expressions using Python `ast` module; something you could do with `sympy`.<jupyter_code>import ast; from ast import *

eq = "7*x"
expr = ast.parse(eq, mode="eval")
print(ast.unparse(expr), ast.dump(expr, indent=4), sep="\n\n")
def derivate(expr, dx: str):
    match expr:
        case Expression(e):
            return Expression(derivate(e, dx))
        case Constant():                                                # d/dx C = 0
            return Constant(0)
        case Name(x) if x == dx:                                        # d/dx x = 1
            return Constant(1)
        case Name(x) if x != dx:                                        # d/dx y = 0
            return Constant(0)
        case BinOp(Name(x), Pow(), Constant(a)) if x == dx:             # d/dx x^a = ax^(a-1)
            return BinOp(Constant(a),
                         Mult(),
                         BinOp(Name(x), Pow(), Constant(a-1)))
        case BinOp(lhs, Add(), rhs):                                    # (a+b)' = a' + b'
            return BinOp(derivate(lhs, dx), Add(), derivate(rhs, dx))
        case BinOp(lhs, Mult(), rhs):                                   # (ab)' = a'b + ab'
            return BinOp(BinOp(derivate(lhs, dx), Mult(), rhs),
                         Add(),
                         BinOp(lhs, Mult(), derivate(rhs, dx)))
        case _:
            raise NotImplementedError(f"{expr!r}")
eq = "7*x"
expr = ast.parse(eq, mode="eval")
expr_prime = derivate(expr, "x")
print(ast.unparse(expr_prime), ast.dump(expr_prime, indent=4), sep="\n\n")
def simplify(expr):
    # recurse
    match expr:
        case Expression(e):
            return Expression(simplify(e))
        case BinOp(rhs, op, lhs):
            expr = BinOp(simplify(rhs), op, simplify(lhs))
    
    # simplify
    match expr:
        case BinOp(_, Mult(), Constant(0)) | BinOp(Constant(0), Mult(), _):
            return Constant(0)
        case BinOp(x, Add(),  Constant(0)) | BinOp(Constant(0), Add(), x)  | \
             BinOp(x, Mult(), Constant(1)) | BinOp(Constant(1), Mult(), x) | \
             BinOp(x, Pow(),  Constant(1)):
            return x
        case _:
            return expr
#eq = "7*x"
eq = "4*x**9 + 12*x**3 + 34*x*y + y + 5"
expr = ast.parse(eq, mode="eval")
expr_prime = derivate(expr, "x")
expr_prime_simple = simplify(expr_prime)
print(ast.unparse(expr_prime), ast.unparse(expr_prime_simple), sep="\n\n")<jupyter_output>0 * x ** 9 + 4 * (9 * x ** 8) + (0 * x ** 3 + 12 * (3 * x ** 2)) + ((0 * x + 34 * 1) * y + 34 * x * 0) + 0 + 0

4 * (9 * x ** 8) + 12 * (3 * x ** 2) + 34 * y<jupyter_text>Addendum How to compile Python-3.10.0a6 on Ubuntu 20.04 LTS```shsudo apt install python3-dev libffi-dev libsqlite3-dev./configure && make -j 4./python -m ensurepip./python -m pip install jupyter```<jupyter_code># set Jupyter theme: https://github.com/dunovank/jupyter-themes
!jt -t grade3<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1454915254237288, 'fraction_numerical': 0.026440677966101694, 'mean_word_length': 2.360364464692483, 'pattern_counts': {'":': 17, '<': 29, '<?xml version=': 0, '>': 29, 'https://': 6, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 21, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '70481', 'license': 'MIT', 'n_tokens_mistral': 2487, 'n_tokens_neox': 2332, 'n_words': 604, 'path': '002--python-3-10-pattern-matching/structural-pattern-matching.ipynb', 'repo_name': 'tkarabela/bigpython', 'size': '7383'}