1"""
2Python Pattern Matching (Python 3.10+)
3
4Demonstrates:
5- match/case statement basics
6- Literal patterns
7- Capture patterns
8- Wildcard patterns
9- OR patterns
10- Guards (if clauses)
11- Class patterns
12- Sequence patterns
13- Mapping patterns
14- AS patterns
15"""
16
17from dataclasses import dataclass
18from typing import Any
19
20
21def section(title: str) -> None:
22 """Print a section header."""
23 print("\n" + "=" * 60)
24 print(f" {title}")
25 print("=" * 60)
26
27
28# =============================================================================
29# Basic Pattern Matching
30# =============================================================================
31
32section("Basic Pattern Matching")
33
34
35def describe_number(n: int) -> str:
36 """Match literal values."""
37 match n:
38 case 0:
39 return "zero"
40 case 1:
41 return "one"
42 case 2:
43 return "two"
44 case _:
45 return f"many ({n})"
46
47
48print(f"describe_number(0): {describe_number(0)}")
49print(f"describe_number(1): {describe_number(1)}")
50print(f"describe_number(42): {describe_number(42)}")
51
52
53# =============================================================================
54# OR Patterns
55# =============================================================================
56
57section("OR Patterns")
58
59
60def classify_status_code(code: int) -> str:
61 """Match multiple values."""
62 match code:
63 case 200 | 201 | 202:
64 return "Success"
65 case 400 | 401 | 403 | 404:
66 return "Client Error"
67 case 500 | 502 | 503:
68 return "Server Error"
69 case _:
70 return "Unknown"
71
72
73print(f"Status 200: {classify_status_code(200)}")
74print(f"Status 404: {classify_status_code(404)}")
75print(f"Status 500: {classify_status_code(500)}")
76print(f"Status 999: {classify_status_code(999)}")
77
78
79# =============================================================================
80# Capture Patterns
81# =============================================================================
82
83section("Capture Patterns")
84
85
86def process_point(point: tuple) -> str:
87 """Capture matched values."""
88 match point:
89 case (0, 0):
90 return "Origin"
91 case (0, y):
92 return f"On Y-axis at y={y}"
93 case (x, 0):
94 return f"On X-axis at x={x}"
95 case (x, y):
96 return f"Point at ({x}, {y})"
97
98
99print(f"(0, 0): {process_point((0, 0))}")
100print(f"(0, 5): {process_point((0, 5))}")
101print(f"(3, 0): {process_point((3, 0))}")
102print(f"(3, 4): {process_point((3, 4))}")
103
104
105# =============================================================================
106# Guards (if clauses)
107# =============================================================================
108
109section("Guards (if clauses)")
110
111
112def categorize_point(point: tuple) -> str:
113 """Use guards to add conditions."""
114 match point:
115 case (x, y) if x == y:
116 return f"On diagonal: ({x}, {y})"
117 case (x, y) if x > 0 and y > 0:
118 return f"Quadrant I: ({x}, {y})"
119 case (x, y) if x < 0 and y > 0:
120 return f"Quadrant II: ({x}, {y})"
121 case (x, y) if x < 0 and y < 0:
122 return f"Quadrant III: ({x}, {y})"
123 case (x, y) if x > 0 and y < 0:
124 return f"Quadrant IV: ({x}, {y})"
125 case _:
126 return "On axis or origin"
127
128
129print(f"(5, 5): {categorize_point((5, 5))}")
130print(f"(3, 4): {categorize_point((3, 4))}")
131print(f"(-3, 4): {categorize_point((-3, 4))}")
132print(f"(3, -4): {categorize_point((3, -4))}")
133
134
135# =============================================================================
136# Sequence Patterns
137# =============================================================================
138
139section("Sequence Patterns")
140
141
142def analyze_list(data: list) -> str:
143 """Match sequences."""
144 match data:
145 case []:
146 return "Empty list"
147 case [x]:
148 return f"Single element: {x}"
149 case [x, y]:
150 return f"Two elements: {x}, {y}"
151 case [x, y, z]:
152 return f"Three elements: {x}, {y}, {z}"
153 case [first, *middle, last]:
154 return f"Multiple elements: first={first}, middle={middle}, last={last}"
155
156
157print(f"[]: {analyze_list([])}")
158print(f"[1]: {analyze_list([1])}")
159print(f"[1, 2]: {analyze_list([1, 2])}")
160print(f"[1, 2, 3]: {analyze_list([1, 2, 3])}")
161print(f"[1, 2, 3, 4, 5]: {analyze_list([1, 2, 3, 4, 5])}")
162
163
164# =============================================================================
165# Wildcard and Rest Patterns
166# =============================================================================
167
168section("Wildcard and Rest Patterns")
169
170
171def parse_command(cmd: list) -> str:
172 """Parse command with rest pattern."""
173 match cmd:
174 case ["quit"]:
175 return "Quitting..."
176 case ["help", topic]:
177 return f"Help for: {topic}"
178 case ["create", resource, *options]:
179 return f"Creating {resource} with options: {options}"
180 case ["delete", resource]:
181 return f"Deleting {resource}"
182 case _:
183 return "Unknown command"
184
185
186print(f"['quit']: {parse_command(['quit'])}")
187print(f"['help', 'commands']: {parse_command(['help', 'commands'])}")
188print(f"['create', 'user', '--admin']: {parse_command(['create', 'user', '--admin'])}")
189print(f"['create', 'file', 'a.txt', 'b.txt']: {parse_command(['create', 'file', 'a.txt', 'b.txt'])}")
190
191
192# =============================================================================
193# Mapping Patterns
194# =============================================================================
195
196section("Mapping Patterns")
197
198
199def handle_event(event: dict) -> str:
200 """Match dictionary patterns."""
201 match event:
202 case {"type": "click", "x": x, "y": y}:
203 return f"Click at ({x}, {y})"
204 case {"type": "keypress", "key": key}:
205 return f"Key pressed: {key}"
206 case {"type": "scroll", "direction": direction, **rest}:
207 return f"Scroll {direction}, data: {rest}"
208 case {"type": event_type}:
209 return f"Event: {event_type} (no additional data)"
210 case _:
211 return "Unknown event"
212
213
214print(handle_event({"type": "click", "x": 100, "y": 200}))
215print(handle_event({"type": "keypress", "key": "Enter"}))
216print(handle_event({"type": "scroll", "direction": "down", "delta": 10}))
217print(handle_event({"type": "custom"}))
218
219
220# =============================================================================
221# Class Patterns
222# =============================================================================
223
224section("Class Patterns")
225
226
227@dataclass
228class Point:
229 x: float
230 y: float
231
232
233@dataclass
234class Circle:
235 center: Point
236 radius: float
237
238
239@dataclass
240class Rectangle:
241 top_left: Point
242 width: float
243 height: float
244
245
246def describe_shape(shape) -> str:
247 """Match dataclass instances."""
248 match shape:
249 case Point(x=0, y=0):
250 return "Point at origin"
251 case Point(x=x, y=y):
252 return f"Point at ({x}, {y})"
253 case Circle(center=Point(x=cx, y=cy), radius=r):
254 return f"Circle centered at ({cx}, {cy}) with radius {r}"
255 case Rectangle(top_left=Point(x=x, y=y), width=w, height=h):
256 return f"Rectangle at ({x}, {y}), size {w}x{h}"
257 case _:
258 return "Unknown shape"
259
260
261p = Point(5, 10)
262c = Circle(Point(0, 0), 5)
263r = Rectangle(Point(10, 20), 30, 40)
264
265print(describe_shape(p))
266print(describe_shape(c))
267print(describe_shape(r))
268
269
270# =============================================================================
271# Nested Patterns
272# =============================================================================
273
274section("Nested Patterns")
275
276
277def evaluate(expr: list) -> float:
278 """Simple expression evaluator with nested patterns."""
279 match expr:
280 case ["+", a, b]:
281 return evaluate(a) + evaluate(b)
282 case ["-", a, b]:
283 return evaluate(a) - evaluate(b)
284 case ["*", a, b]:
285 return evaluate(a) * evaluate(b)
286 case ["/", a, b]:
287 return evaluate(a) / evaluate(b)
288 case int(n) | float(n):
289 return n
290 case _:
291 raise ValueError(f"Invalid expression: {expr}")
292
293
294expr1 = ["+", 10, 5]
295expr2 = ["*", ["+", 3, 2], 4]
296expr3 = ["/", ["-", 20, 5], 3]
297
298print(f"{expr1} = {evaluate(expr1)}")
299print(f"{expr2} = {evaluate(expr2)}")
300print(f"{expr3} = {evaluate(expr3)}")
301
302
303# =============================================================================
304# AS Patterns
305# =============================================================================
306
307section("AS Patterns (Capture and Match)")
308
309
310def process_data(data: Any) -> str:
311 """Use AS patterns to capture matched values."""
312 match data:
313 case [x, y] as point:
314 return f"2D point {point}: x={x}, y={y}"
315 case [x, y, z] as point:
316 return f"3D point {point}: x={x}, y={y}, z={z}"
317 case {"name": name, "age": age} as person:
318 return f"Person {person}: {name} is {age} years old"
319 case _:
320 return "Unknown data format"
321
322
323print(process_data([1, 2]))
324print(process_data([1, 2, 3]))
325print(process_data({"name": "Alice", "age": 30}))
326
327
328# =============================================================================
329# Type Patterns
330# =============================================================================
331
332section("Type Patterns")
333
334
335def handle_value(value: Any) -> str:
336 """Match by type."""
337 match value:
338 case int(n) if n < 0:
339 return f"Negative integer: {n}"
340 case int(n):
341 return f"Positive integer: {n}"
342 case float(x):
343 return f"Float: {x}"
344 case str(s):
345 return f"String: '{s}'"
346 case list(items):
347 return f"List with {len(items)} items"
348 case dict(mapping):
349 return f"Dict with {len(mapping)} keys"
350 case _:
351 return f"Other type: {type(value).__name__}"
352
353
354print(handle_value(42))
355print(handle_value(-10))
356print(handle_value(3.14))
357print(handle_value("hello"))
358print(handle_value([1, 2, 3]))
359print(handle_value({"a": 1, "b": 2}))
360
361
362# =============================================================================
363# Complex Example: JSON Processing
364# =============================================================================
365
366section("Complex Example: JSON Processing")
367
368
369def process_json(data: dict) -> str:
370 """Process JSON-like structure."""
371 match data:
372 case {
373 "type": "user",
374 "name": str(name),
375 "email": str(email),
376 "age": int(age)
377 } if age >= 18:
378 return f"Adult user: {name} ({email})"
379
380 case {
381 "type": "user",
382 "name": str(name),
383 "age": int(age)
384 } if age < 18:
385 return f"Minor user: {name}"
386
387 case {
388 "type": "post",
389 "title": str(title),
390 "author": str(author),
391 "tags": list(tags)
392 }:
393 return f"Post '{title}' by {author}, tags: {tags}"
394
395 case {
396 "type": "comment",
397 "text": str(text),
398 "replies": list(replies)
399 }:
400 return f"Comment with {len(replies)} replies: '{text}'"
401
402 case {"type": type_name, **rest}:
403 return f"Unknown type '{type_name}' with data: {rest}"
404
405 case _:
406 return "Invalid JSON structure"
407
408
409user1 = {"type": "user", "name": "Alice", "email": "alice@example.com", "age": 25}
410user2 = {"type": "user", "name": "Bob", "age": 16}
411post = {"type": "post", "title": "Python Tips", "author": "Charlie", "tags": ["python", "programming"]}
412comment = {"type": "comment", "text": "Great post!", "replies": []}
413
414print(process_json(user1))
415print(process_json(user2))
416print(process_json(post))
417print(process_json(comment))
418
419
420# =============================================================================
421# State Machine Example
422# =============================================================================
423
424section("State Machine Example")
425
426
427def transition(state: str, event: tuple) -> tuple[str, str]:
428 """Simple state machine using pattern matching."""
429 match (state, event):
430 case ("idle", ("start",)):
431 return ("running", "Started")
432
433 case ("running", ("pause",)):
434 return ("paused", "Paused")
435
436 case ("paused", ("resume",)):
437 return ("running", "Resumed")
438
439 case ("running", ("stop",)):
440 return ("idle", "Stopped")
441
442 case ("paused", ("stop",)):
443 return ("idle", "Stopped from pause")
444
445 case (current_state, (event_name,)):
446 return (current_state, f"Invalid event '{event_name}' in state '{current_state}'")
447
448
449state = "idle"
450events = [("start",), ("pause",), ("resume",), ("stop",)]
451
452print(f"Initial state: {state}")
453for event in events:
454 state, message = transition(state, event)
455 print(f" Event {event[0]:6} -> state: {state:8} ({message})")
456
457
458# =============================================================================
459# Summary
460# =============================================================================
461
462section("Summary")
463
464print("""
465Pattern matching features (Python 3.10+):
4661. match/case - structural pattern matching
4672. Literal patterns - exact value matching
4683. Capture patterns - bind matched values to variables
4694. Wildcard (_) - match anything, don't capture
4705. OR patterns (|) - match multiple alternatives
4716. Guards (if) - add conditions to patterns
4727. Sequence patterns - match lists/tuples
4738. Mapping patterns - match dictionaries
4749. Class patterns - match dataclass/class instances
47510. AS patterns - capture while matching
47611. Type patterns - match by type
47712. Nested patterns - combine patterns
478
479Benefits:
480- More readable than if/elif chains
481- Exhaustiveness checking (with mypy)
482- Destructuring built-in
483- Declarative style
484- Better for complex matching scenarios
485
486Use cases:
487- Command parsing
488- Event handling
489- JSON/API response processing
490- State machines
491- Expression evaluation
492- Data validation
493
494Pattern matching vs if/elif:
495- Patterns: Better for structural matching, destructuring
496- if/elif: Better for simple conditions, complex logic
497
498Note: Pattern matching requires Python 3.10+
499""")