#!/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')