Have you ever faced this dilemma? Using
if-elifto handle complex data makes the code as tangled as spaghetti; without type checking,AttributeErroralways pops up at runtime; debugging involves guessing types by looking at variable names… The structured pattern matching in Python 3.10+ (match-case) combined with type annotations allows you to write code that is both elegant and reliable! — Structured pattern matching + type hints enable you to write Python code with bank-level reliability!
The Three Major Pain Points of Traditional Code
Pain Point 1: Nested Conditions Lead to Readability Disasters
# Traditional way to handle API responses
def process_api_response(response):
if isinstance(response, dict):
if "status" in response:
if response["status"] == "success":
if "data" in response:
print("Successful data:", response["data"])
else:
print("Success but no data")
else:
print("Request failed:", response.get("error"))
else:
print("Invalid response format")
else:
print("Expected a dictionary but received:", type(response))
Problem:
- 5 levels of nesting, making it hard to read
- Each condition requires separate type checking
- Easy to miss branches when modifying
Pain Point 2: Lack of Type Checking, Errors Only Reported at Runtime
# Hidden dangers without type hints
def calculate(a, b, op):
if op == "+":
return a + b # If a/b is a string, it will raise an error
elif op == "-":
return a - b
# ...other operations
Problem:
- Parameter types are unclear
- Type errors are exposed only at runtime
- In collaborative development, interfaces need to be confirmed repeatedly
Pain Point 3: Repeatedly Deconstructing Data, Code is Bulky
# Traditional way to deconstruct JSON
def parse_user(json_data):
if isinstance(json_data, dict):
user = json_data.get("user")
if isinstance(user, dict):
name = user.get("name")
age = user.get("age")
# ...more fields
Problem:
- Layer upon layer of
.get()calls - High code duplication
- Easy to overlook field checks
Python 3.10+ Solutions: match-case + Type Annotations
1. Structured Pattern Matching: Clear as Solving a Math Problem
The match-case in Python 3.10 allows you to match data by structure, and combined with type annotations, the code logic is clear at a glance:
from typing import Any, Dict, Optional, Union
def process_api_response(response: Union[Dict[str, Any], list]) -> None:
match response:
case {"status": "success", "data": data}: # Match successful response
print("Successful data:", data)
case {"status": "error", "error": error_msg}: # Match error response
print("Request failed:", error_msg)
case _: # Default branch
print("Invalid response format")
# Test
process_api_response({"status": "success", "data": [1, 2, 3]})
# Output: Successful data: [1, 2, 3]
Advantages:
- One line of code matches the complete structure
- Type annotations clarify parameter requirements
- Default branch handles unexpected situations
2. Type Annotations + Pattern Matching: Bank-Level Reliability
By combining TypedDict and Literal, you can define precise data structures:
from typing import Literal, TypedDict
class APIResponse(TypedDict):
status: Literal["success", "error"]
data: list # Exists when successful
error: Optional[str] # Exists when failed
def safe_process(response: APIResponse) -> None:
match response:
case {"status": "success", "data": data}:
print("Processing data:", sum(data)) # Clearly know data is a list
case {"status": "error", "error": msg}:
print("Error:", msg)
# Test (in actual development, mypy can be used to check types)
safe_process({"status": "success", "data": [1, 2, 3]}) # Output: Processing data: 6
Effects:
- Compile-time type checking (with mypy)
- Runtime pattern matching provides double assurance
- Code self-documents, no need for comments
3. Deconstructing Complex Data: Extract All Fields in One Line
When handling nested JSON, match-case can directly deconstruct multi-layer structures:
from typing import TypedDict
class User(TypedDict):
name: str
age: int
address: Dict[str, str]
class APIResult(TypedDict):
user: User
timestamp: float
def extract_user_info(result: APIResult) -> None:
match result:
case {"user": {"name": name, "age": age, "address": {"city": city}}, "timestamp": _}:
print(f"{name}, {age} years old, from {city}")
# Test
extract_user_info({
"user": {"name": "Zhang San", "age": 30, "address": {"city": "Beijing"}},
"timestamp": 1630000000.0
})
# Output: Zhang San, 30 years old, from Beijing
Comparison with Traditional Writing:
# Traditional way requires multi-layer deconstruction
def traditional_extract(result):
user = result.get("user")
if user:
name = user.get("name")
age = user.get("age")
address = user.get("address")
if address:
city = address.get("city")
# ...final use of data
Three Steps to Write Reliable Code
- Define Types: Use
TypedDict/Literalto clarify data structures - Pattern Matching: Use
match-caseto process data by structure - Type Checking: Run
mypyto check for type errors
Example: Complete API Processing Flow
from typing import Literal, TypedDict, Optional
class UserData(TypedDict):
id: int
name: str
class APIResponse(TypedDict):
status: Literal["success", "error"]
user: Optional[UserData]
error: Optional[str]
def handle_api(response: APIResponse) -> None:
match response:
case {"status": "success", "user": {"id": uid, "name": name}}:
print(f"User ID: {uid}, Name: {name}")
case {"status": "error", "error": msg}:
print(f"API Error: {msg}")
case _:
print("Invalid response")
# Test
handle_api({"status": "success", "user": {"id": 1, "name": "Li Si"}})
# Output: User ID: 1, Name: Li Si
Hands-On Practice: Transform Your Code
- Find a function that processes JSON/dictionaries
- Use
TypedDictto define input/output types - Use
match-caseto refactor logic branches - Run
mypyto check for type errors
Thought Question: If there is a function that needs to handle the following three formats of data, how would you implement it using match-case + type annotations?
{