#!/usr/bin/env python # coding: utf-8 # #
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 # In[1]: text = "18:32:50" #text = "18:32" # 🔧 # ↘️ h, m, s = text.split(":") print(f"{h} hours, {m} minutes and {s} seconds") # # # #### ...with "for" # In[2]: 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}") # #### ...with try/except # In[3]: 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})") #
# # ### Now we can use "match" # In[5]: 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") # ##
Let's have a look at 3 examples
# # # 1️⃣ Event Queue

# 2️⃣ JSON API

# 3️⃣ Tree Traversal #
# #


# ## Example 1: Event Queue # #
# # Snippet from previous Big Python tutorial (event-driven Snake in PyGame, [link](https://youtu.be/_1KkTIxAZGg)). # In[ ]: # 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()) # In[ ]: # 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()) #


# ## Example 2: JSON API #
# With mapping and string literal patterns, processing JSON dicts is easy. # # Let's look at commits in the Flask GitHub repository. # In[ ]: 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 # In[ ]: # Python 3.9 for item in data["items"]: sha = item["sha"] message = item["commit"]["message"] name = item["commit"]["author"]["name"] # In[ ]: # 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": "Armin Ronacher"}}}: #


# ## Example 3: Tree Traversal # #
# Functional 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`. # In[7]: 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") # In[8]: 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}") # In[9]: 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") # In[10]: 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 # In[11]: #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") #
#
#
# # ## Addendum # #### How to compile Python-3.10.0a6 on Ubuntu 20.04 LTS # #
# # ```sh # sudo apt install python3-dev libffi-dev libsqlite3-dev # ./configure && make -j 4 # ./python -m ensurepip # ./python -m pip install jupyter # ``` # In[ ]: # set Jupyter theme: https://github.com/dunovank/jupyter-themes get_ipython().system('jt -t grade3')