现代语法:字典合并、walrus 与 match
目标:能够在真实数据处理中判断何时使用字典合并、赋值表达式和结构化模式匹配,而不是把它们当作更短的旧语法。
现代语法的价值不在于减少行数,而在于让一个常见意图更直接。
base | overrides创建新字典,右侧同名键覆盖左侧;base |= overrides原地更新。name := expression在表达式中保存结果,适合“计算一次、判断并复用”,不适合把多步状态藏进条件。match/case匹配的是数据结构,可解构 mapping、sequence 和 class;它不只是 switch。
完整示例:解析 provider 响应
保存为 syntax_lab.py:
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class Success:
text: str
tokens: int
@dataclass(frozen=True)
class Failure:
code: int
message: str
def parse_response(value: object) -> Success | Failure:
match value:
case {"status": "ok", "data": {"text": str(text), "tokens": int(tokens)}}:
return Success(text=text, tokens=tokens)
case {"status": "error", "code": int(code), "message": str(message)}:
return Failure(code=code, message=message)
case ["stream", str(first), *rest] if first:
joined = "".join([first, *(str(part) for part in rest)])
return Success(text=joined, tokens=0)
case _:
raise ValueError("unsupported response shape")
def apply_defaults(payload: dict[str, object]) -> dict[str, object]:
defaults: dict[str, object] = {"language": "zh", "priority": 0}
return defaults | payload
if __name__ == "__main__":
parsed = parse_response(
{"status": "ok", "data": {"text": "hello", "tokens": 3}}
)
config = apply_defaults({"priority": 5})
print(parsed)
print(config)
运行:
uv run python syntax_lab.py
预期输出:
Success(text='hello', tokens=3)
{'language': 'zh', 'priority': 5}
mapping pattern 只要求所列键存在,额外键默认不会让匹配失败。str(text) 和 int(tokens) 是 class pattern:检查类型并绑定完整值。guard if first 只在 sequence pattern 成功后执行。
walrus 的适用边界
适合:
if (cleaned := raw.strip()):
messages.append(cleaned)
raw.strip() 只计算一次,cleaned 紧邻其使用位置。不适合:
# 不要把多个状态变化压进一条难读条件
# if (item := next_item()) and (score := grade(item)) > limit and update(score):
一旦条件同时产生多个后续状态,普通赋值与多个 if 更便于调试。
引导实验:观察 mapping 的额外键
给成功响应增加 "request_id": "r1",运行 parser。它仍会成功。若你需要拒绝未知键,match 不是完整 schema validator;应在 Pydantic 或显式检查中表达该规则。
接着把 tokens 改成 True。由于 bool 是 int 的子类,int(tokens) 会匹配。这是 Python 对象模型的真实结果。若业务上 bool 不合法,应加 guard:
case {"tokens": int(tokens)} if not isinstance(tokens, bool):
独立练习
新增 {"status": "retry", "after": number} 分支,要求 after > 0,返回 Failure(429, ...)。其他非正值仍应进入默认错误。
参考实现
case {"status": "retry", "after": int(seconds)} if seconds > 0:
return Failure(code=429, message=f"retry after {seconds}s")
验收:
assert parse_response({"status": "retry", "after": 2}) == Failure(429, "retry after 2s")
try:
parse_response({"status": "retry", "after": 0})
except ValueError:
pass
else:
raise AssertionError("zero delay must be rejected")