python-by-example/02_control_flow/03_match_case.py
2025-12-30 08:50:00 +02:00

244 lines
8.3 KiB
Python

"""
================================================================================
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')}")