""" ================================================================================ File: 03_match_case.py Topic: Pattern Matching with match-case (Python 3.10+) ================================================================================ This file demonstrates Python's structural pattern matching introduced in Python 3.10. It's similar to switch/case in other languages but much more powerful with pattern matching capabilities. Key Concepts: - Basic match-case syntax - The underscore (_) as wildcard/default case - Pattern matching with guards - Matching sequences and mappings - Capturing values in patterns Note: This requires Python 3.10 or later! ================================================================================ """ # ----------------------------------------------------------------------------- # 1. Basic match-case (Similar to switch/case) # ----------------------------------------------------------------------------- # The simplest form of pattern matching def get_day_name(day_number: int) -> str: """Convert day number to day name.""" match day_number: case 1: return "Monday" case 2: return "Tuesday" case 3: return "Wednesday" case 4: return "Thursday" case 5: return "Friday" case 6: return "Saturday" case 7: return "Sunday" case _: # Default case (underscore is wildcard) return "Invalid day" print("--- Basic match-case ---") print(f"Day 1: {get_day_name(1)}") print(f"Day 5: {get_day_name(5)}") print(f"Day 9: {get_day_name(9)}") # ----------------------------------------------------------------------------- # 2. Multiple Patterns in One Case # ----------------------------------------------------------------------------- # Use the | operator to match multiple values def categorize_day(day: str) -> str: """Categorize day as weekday or weekend.""" match day.lower(): case "saturday" | "sunday": return "Weekend - Time to relax!" case "monday" | "tuesday" | "wednesday" | "thursday" | "friday": return "Weekday - Time to work!" case _: return "Unknown day" print("\n--- Multiple Patterns ---") print(f"Saturday: {categorize_day('Saturday')}") print(f"Monday: {categorize_day('Monday')}") # ----------------------------------------------------------------------------- # 3. Pattern Matching with Guards # ----------------------------------------------------------------------------- # Add conditions using 'if' after the pattern def evaluate_score(score: int) -> str: """Evaluate score with guards.""" match score: case n if n < 0: return "Invalid score (negative)" case n if n > 100: return "Invalid score (over 100)" case n if n >= 90: return f"Grade A - Excellent! ({n}%)" case n if n >= 80: return f"Grade B - Good! ({n}%)" case n if n >= 70: return f"Grade C - Satisfactory ({n}%)" case n if n >= 60: return f"Grade D - Pass ({n}%)" case _: return f"Grade F - Fail ({score}%)" print("\n--- Pattern with Guards ---") print(evaluate_score(95)) print(evaluate_score(75)) print(evaluate_score(45)) print(evaluate_score(-10)) # ----------------------------------------------------------------------------- # 4. Matching Sequences (Tuples/Lists) # ----------------------------------------------------------------------------- # Match against list or tuple patterns def describe_point(point): """Describe a point based on its coordinates.""" match point: case (0, 0): return "Origin" case (0, y): return f"On Y-axis at y={y}" case (x, 0): return f"On X-axis at x={x}" case (x, y) if x == y: return f"On diagonal at ({x}, {y})" case (x, y): return f"Point at ({x}, {y})" case _: return "Not a valid point" print("\n--- Matching Sequences ---") print(describe_point((0, 0))) print(describe_point((0, 5))) print(describe_point((3, 0))) print(describe_point((4, 4))) print(describe_point((3, 7))) # ----------------------------------------------------------------------------- # 5. Matching with Variable Length Sequences # ----------------------------------------------------------------------------- # Use * to capture remaining elements def analyze_list(data): """Analyze list structure.""" match data: case []: return "Empty list" case [single]: return f"Single element: {single}" case [first, second]: return f"Two elements: {first} and {second}" case [first, *middle, last]: return f"First: {first}, Last: {last}, Middle count: {len(middle)}" case _: return "Not a list" print("\n--- Variable Length Matching ---") print(analyze_list([])) print(analyze_list([42])) print(analyze_list([1, 2])) print(analyze_list([1, 2, 3, 4, 5])) # ----------------------------------------------------------------------------- # 6. Matching Dictionaries # ----------------------------------------------------------------------------- # Match against dictionary patterns def process_request(request): """Process different types of requests.""" match request: case {"type": "login", "user": username}: return f"User '{username}' is logging in" case {"type": "logout", "user": username}: return f"User '{username}' is logging out" case {"type": "message", "user": username, "content": content}: return f"Message from '{username}': {content}" case {"type": action}: return f"Unknown action: {action}" case _: return "Invalid request format" print("\n--- Matching Dictionaries ---") print(process_request({"type": "login", "user": "Baraa"})) print(process_request({"type": "message", "user": "Ali", "content": "Hello!"})) print(process_request({"type": "logout", "user": "Sara"})) print(process_request({"type": "unknown_action"})) # ----------------------------------------------------------------------------- # 7. Matching Class Instances # ----------------------------------------------------------------------------- # Match against object attributes class Point: """Simple point class for demonstration.""" def __init__(self, x, y): self.x = x self.y = y def classify_point(point): """Classify point using class matching.""" match point: case Point(x=0, y=0): return "At origin" case Point(x=0, y=y): return f"On Y-axis at {y}" case Point(x=x, y=0): return f"On X-axis at {x}" case Point(x=x, y=y): return f"At ({x}, {y})" case _: return "Not a Point" print("\n--- Matching Class Instances ---") print(classify_point(Point(0, 0))) print(classify_point(Point(0, 7))) print(classify_point(Point(5, 0))) print(classify_point(Point(3, 4))) # ----------------------------------------------------------------------------- # 8. Practical Example: Command Parser # ----------------------------------------------------------------------------- # Real-world use case for a simple command interpreter def execute_command(command): """Parse and execute simple commands.""" parts = command.split() match parts: case ["quit"] | ["exit"] | ["q"]: return "Goodbye!" case ["hello"]: return "Hello there!" case ["hello", name]: return f"Hello, {name}!" case ["add", x, y]: return f"Result: {int(x) + int(y)}" case ["repeat", count, *words]: return " ".join(words) * int(count) case ["help"]: return "Available: quit, hello [name], add x y, repeat n words..." case [unknown, *_]: return f"Unknown command: {unknown}. Type 'help' for assistance." case _: return "Please enter a command" print("\n--- Command Parser Example ---") print(f"'hello': {execute_command('hello')}") print(f"'hello Baraa': {execute_command('hello Baraa')}") print(f"'add 5 3': {execute_command('add 5 3')}") print(f"'repeat 3 Hi': {execute_command('repeat 3 Hi ')}") print(f"'help': {execute_command('help')}") print(f"'xyz': {execute_command('xyz')}")