-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #563 from realpython/structural-pattern-matching
Materials for Structural Pattern Matching: Initial Commit
- Loading branch information
Showing
9 changed files
with
476 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Structural Pattern Matching in Python | ||
|
||
This folder contains the code samples for the Real Python tutorial [Structural Pattern Matching in Python](https://realpython.com/structural-pattern-matching/). | ||
|
||
## Installation | ||
|
||
Create and activate a virtual environment: | ||
|
||
```sh | ||
$ python -m venv venv/ | ||
$ source venv/bin/activate | ||
``` | ||
|
||
Install the required third-party dependencies: | ||
|
||
```sh | ||
(venv) $ python -m pip install -r requirements.txt | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from http.client import HTTPConnection, HTTPResponse, HTTPSConnection | ||
from sys import stderr | ||
from urllib.parse import ParseResult, urlparse | ||
|
||
|
||
def fetch(url): | ||
print(f"Fetching URL: {url}", file=stderr) | ||
connection = make_connection(url) | ||
try: | ||
connection.request("GET", "/") | ||
match connection.getresponse(): | ||
case HTTPResponse(status=code) if code >= 400: | ||
raise ValueError("Failed to fetch URL") | ||
case HTTPResponse(status=code) as resp if ( | ||
code >= 300 and (redirect := resp.getheader("Location")) | ||
): | ||
return fetch(redirect) | ||
case HTTPResponse(status=code) as resp if code >= 200: | ||
return resp.read() | ||
case _: | ||
raise ValueError("Unexpected response") | ||
finally: | ||
connection.close() | ||
|
||
|
||
def make_connection(url): | ||
match urlparse(url): | ||
case ParseResult("http", netloc=host): | ||
return HTTPConnection(host) | ||
case ParseResult("https", netloc=host): | ||
return HTTPSConnection(host) | ||
case _: | ||
raise ValueError("Unsupported URL scheme") | ||
|
||
|
||
if __name__ == "__main__": | ||
fetch("http://realpython.com/") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import random | ||
|
||
MIN, MAX = 1, 100 | ||
MAX_TRIES = 5 | ||
PROMPT_1 = f"\N{mage} Guess a number between {MIN} and {MAX}: " | ||
PROMPT_2 = "\N{mage} Try again: " | ||
BYE = "Bye \N{waving hand sign}" | ||
|
||
|
||
def main(): | ||
print("Welcome to the game! Type 'q' or 'quit' to exit.") | ||
while True: | ||
play_game() | ||
if not want_again(): | ||
bye() | ||
|
||
|
||
def play_game(): | ||
drawn_number = random.randint(MIN, MAX) | ||
num_tries = MAX_TRIES | ||
prompt = PROMPT_1 | ||
while num_tries > 0: | ||
match input(prompt): | ||
case command if command.lower() in ("q", "quit"): | ||
bye() | ||
case user_input: | ||
try: | ||
user_number = int(user_input) | ||
except ValueError: | ||
print("That's not a number!") | ||
else: | ||
match user_number: | ||
case number if number < drawn_number: | ||
num_tries -= 1 | ||
prompt = PROMPT_2 | ||
print(f"Too low! {num_tries} tries left.") | ||
case number if number > drawn_number: | ||
num_tries -= 1 | ||
prompt = PROMPT_2 | ||
print(f"Too high! {num_tries} tries left.") | ||
case _: | ||
print("You won \N{party popper}") | ||
return | ||
print("You lost \N{pensive face}") | ||
|
||
|
||
def want_again(): | ||
while True: | ||
match input("Do you want to play again? [Y/N] ").lower(): | ||
case "y": | ||
return True | ||
case "n": | ||
return False | ||
|
||
|
||
def bye(): | ||
print(BYE) | ||
exit() | ||
|
||
|
||
if __name__ == "__main__": | ||
try: | ||
main() | ||
except (KeyboardInterrupt, EOFError): | ||
print() | ||
bye() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import sys | ||
|
||
|
||
def interpret(code, num_bytes=2**10): | ||
stack, brackets = [], {} | ||
for i, instruction in enumerate(code): | ||
match instruction: | ||
case "[": | ||
stack.append(i) | ||
case "]": | ||
brackets[i], brackets[j] = (j := stack.pop()), i | ||
memory = bytearray(num_bytes) | ||
pointer = ip = 0 | ||
while ip < len(code): | ||
match code[ip]: | ||
case ">": | ||
pointer += 1 | ||
case "<": | ||
pointer -= 1 | ||
case "+": | ||
memory[pointer] += 1 | ||
case "-": | ||
memory[pointer] -= 1 | ||
case ".": | ||
print(chr(memory[pointer]), end="") | ||
case ",": | ||
memory[pointer] = ord(sys.stdin.buffer.read(1)) | ||
case "[" if memory[pointer] == 0: | ||
ip = brackets[ip] | ||
case "]" if memory[pointer] != 0: | ||
ip = brackets[ip] | ||
ip += 1 | ||
|
||
|
||
if __name__ == "__main__": | ||
interpret( | ||
""" | ||
+++++++++++[>++++++>+++++++++>++++++++>++++>+++>+<<<<<<-]>+++ | ||
+++.>++.+++++++..+++.>>.>-.<<-.<.+++.------.--------.>>>+.>-. | ||
""" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import json | ||
import urllib.request | ||
from dataclasses import dataclass | ||
|
||
from rich.console import Console | ||
from rich.markdown import Markdown | ||
from rich.panel import Panel | ||
|
||
|
||
@dataclass | ||
class Comment: | ||
when: str | ||
body: str | ||
url: str | ||
user: str | ||
user_url: str | ||
issue_title: str | ||
|
||
@property | ||
def footer(self): | ||
return ( | ||
f"Comment by [{self.user}]({self.user_url})" | ||
f" on [{self.when}]({self.url})" | ||
) | ||
|
||
def render(self): | ||
return Panel( | ||
Markdown(f"{self.body}\n\n---\n_{self.footer}_"), | ||
title=self.issue_title, | ||
padding=1, | ||
) | ||
|
||
|
||
def fetch_github_events(org, repo): | ||
url = f"https://api.github.com/repos/{org}/{repo}/events" | ||
with urllib.request.urlopen(url) as response: | ||
return json.loads(response.read()) | ||
|
||
|
||
def filter_comments(events): | ||
for event in events: | ||
match event: | ||
case { | ||
"type": "IssueCommentEvent", | ||
"created_at": when, | ||
"actor": { | ||
"display_login": user, | ||
}, | ||
"payload": { | ||
"action": "created", | ||
"issue": { | ||
"state": "open", | ||
"title": issue_title, | ||
}, | ||
"comment": { | ||
"body": body, | ||
"html_url": url, | ||
"user": { | ||
"html_url": user_url, | ||
}, | ||
}, | ||
}, | ||
}: | ||
yield Comment(when, body, url, user, user_url, issue_title) | ||
|
||
|
||
def main(): | ||
console = Console() | ||
events = fetch_github_events("python", "cpython") | ||
for comment in filter_comments(events): | ||
console.clear() | ||
console.print(comment.render()) | ||
console.input("\nPress [b]ENTER[/b] for the next comment...") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import ast | ||
import inspect | ||
import textwrap | ||
|
||
|
||
def main(): | ||
source_code = inspect.getsource(sample_function) | ||
source_tree = ast.parse(source_code) | ||
target_tree = optimize(source_tree) | ||
target_code = ast.unparse(target_tree) | ||
|
||
print("Before:") | ||
print(ast.dump(source_tree)) | ||
print(textwrap.indent(source_code, "| ")) | ||
|
||
print("After:") | ||
print(ast.dump(target_tree)) | ||
print(textwrap.indent(target_code, "| ")) | ||
|
||
|
||
def sample_function(): | ||
return 40 + 2 | ||
|
||
|
||
def optimize(node): | ||
match node: | ||
case ast.Module(body, type_ignores): | ||
return ast.Module( | ||
[optimize(child) for child in body], type_ignores | ||
) | ||
case ast.FunctionDef(): | ||
return ast.FunctionDef( | ||
name=node.name, | ||
args=node.args, | ||
body=[optimize(child) for child in node.body], | ||
decorator_list=node.decorator_list, | ||
returns=node.returns, | ||
type_comment=node.type_comment, | ||
type_params=node.type_params, | ||
lineno=node.lineno, | ||
) | ||
case ast.Return(value): | ||
return ast.Return(value=optimize(value)) | ||
case ast.BinOp(ast.Constant(left), op, ast.Constant(right)): | ||
match op: | ||
case ast.Add(): | ||
return ast.Constant(left + right) | ||
case ast.Sub(): | ||
return ast.Constant(left - right) | ||
case _: | ||
return node | ||
case _: | ||
return node | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import ast | ||
import sys | ||
import traceback | ||
|
||
PROMPT = "\N{snake} " | ||
COMMANDS = ("help", "exit", "quit") | ||
|
||
|
||
def main(): | ||
print('Type "help" for more information, "exit" or "quit" to finish.') | ||
while True: | ||
try: | ||
match input(PROMPT): | ||
case command if command.lower() in COMMANDS: | ||
match command.lower(): | ||
case "help": | ||
print(f"Python {sys.version}") | ||
case "exit" | "quit": | ||
break | ||
case expression if valid(expression, "eval"): | ||
_ = eval(expression) | ||
if _ is not None: | ||
print(_) | ||
case statement if valid(statement, "exec"): | ||
exec(statement) | ||
case _: | ||
print("Please type a command or valid Python") | ||
except KeyboardInterrupt: | ||
print("\nKeyboardInterrupt") | ||
except EOFError: | ||
print() | ||
exit() | ||
except Exception: | ||
traceback.print_exc(file=sys.stdout) | ||
|
||
|
||
def valid(code, mode): | ||
try: | ||
ast.parse(code, mode=mode) | ||
return True | ||
except SyntaxError: | ||
return False | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Oops, something went wrong.