类型系统与常见陷阱
Python 是动态类型语言,但类型注解让代码更安全——理解可变性和常见陷阱是写出健壮代码的关键。
类型注解体系
graph TD
TYPE[Python 类型系统] --> BASIC[基础类型]
TYPE --> GENERIC[泛型注解]
TYPE --> ADVANCED[高级类型]
TYPE --> CHECK[静态检查]
BASIC --> INT[int / float / str / bool]
BASIC --> NONE[None / Optional]
GENERIC --> LIST["list[str]"]
GENERIC --> DICT["dict[str, int]"]
GENERIC --> TUPLE["tuple[int, ...]"]
ADVANCED --> UNION["str | int (3.10+)"]
ADVANCED --> LITERAL["Literal['a', 'b']"]
ADVANCED --> TYPED["TypedDict"]
ADVANCED --> PROTO[Protocol]
CHECK --> MYPY[mypy]
CHECK --> PYRIGHT[pyright]
CHECK --> RUFF[ruff]
style TYPE fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
style ADVANCED fill:#fff3e0,stroke:#f57c00,stroke-width:2px
类型注解实战
"""
Python 类型注解:从入门到实战
"""
from typing import Optional, TypedDict, Protocol
from dataclasses import dataclass
# === 基础注解 ===
def greet(name: str, times: int = 1) -> str:
"""函数参数和返回值注解"""
return f"Hello, {name}! " * times
# 变量注解
age: int = 25
names: list[str] = ["Alice", "Bob"]
config: dict[str, int | str] = {"timeout": 30, "host": "localhost"}
# === Optional 与 None ===
def find_user(user_id: int) -> Optional[dict]:
"""可能返回 None 的函数"""
users = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
return users.get(user_id) # 找不到返回 None
# Python 3.10+ 写法
def find_user_new(user_id: int) -> dict | None:
"""等价写法(推荐)"""
return {"id": user_id} if user_id > 0 else None
# === TypedDict ===
class UserProfile(TypedDict):
"""结构化字典类型"""
name: str
age: int
email: str
is_active: bool
def create_profile(name: str, age: int) -> UserProfile:
return {
"name": name,
"age": age,
"email": f"{name.lower()}@example.com",
"is_active": True,
}
# === Protocol(结构化子类型)===
class Printable(Protocol):
"""任何有 __str__ 方法的对象"""
def __str__(self) -> str: ...
def log_item(item: Printable) -> None:
"""接受任何可打印对象"""
print(f"[LOG] {item}")
# === dataclass 替代字典 ===
@dataclass
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
workers: int = 4
def url(self) -> str:
return f"http://{self.host}:{self.port}"
cfg = Config(host="0.0.0.0", debug=True)
print(cfg.url()) # http://0.0.0.0:8080
常见陷阱
| 陷阱 | 问题 | 正确做法 |
|---|---|---|
| 可变默认参数 | def f(items=[]) 所有调用共享同一列表 | def f(items=None) |
is vs == | a is b 比较的是地址不是值 | 值比较用 ==,仅 None 用 is |
| 浮点精度 | 0.1 + 0.2 != 0.3 | 用 math.isclose() 或 Decimal |
| 闭包陷阱 | 循环中 lambda 捕获变量引用 | lambda x=x: x 立即绑定 |
| 字符串拼接 | 循环中 += 拼接字符串 O(n²) | 用 "".join(parts) |
| 深浅拷贝 | b = a[:] 只复制一层 | import copy; copy.deepcopy(a) |
"""
Python 常见陷阱示例
"""
# ❌ 陷阱 1:可变默认参数
def add_item_wrong(item, items=[]):
"""Bug: 所有调用共享同一个 list"""
items.append(item)
return items
# ✅ 正确写法
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
# ❌ 陷阱 2:闭包中的循环变量
funcs_wrong = [lambda: i for i in range(5)]
print([f() for f in funcs_wrong]) # [4, 4, 4, 4, 4] 全是 4!
# ✅ 正确写法
funcs_right = [lambda i=i: i for i in range(5)]
print([f() for f in funcs_right]) # [0, 1, 2, 3, 4]
# ❌ 陷阱 3:浮点精度
print(0.1 + 0.2 == 0.3) # False!
# ✅ 正确写法
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True
# 金融计算用 Decimal
from decimal import Decimal
price = Decimal("19.99") + Decimal("0.01")
print(price) # 20.00 精确
# ❌ 陷阱 4:字典遍历中修改
data = {"a": 1, "b": 2, "c": 3}
# for k in data: # RuntimeError!
# if data[k] < 2:
# del data[k]
# ✅ 正确写法
data = {k: v for k, v in data.items() if v >= 2}
# ❌ 陷阱 5:深浅拷贝
matrix = [[1, 2], [3, 4]]
shallow = matrix[:] # 浅拷贝:内部列表仍是引用
shallow[0][0] = 99
print(matrix[0][0]) # 99 被改了!
import copy
matrix = [[1, 2], [3, 4]]
deep = copy.deepcopy(matrix) # 深拷贝:完全独立
deep[0][0] = 99
print(matrix[0][0]) # 1 不受影响
静态检查工具对比
| 工具 | 速度 | 严格度 | 生态 | 推荐场景 |
|---|---|---|---|---|
| mypy | 中 | 高 | 最成熟 | 大型项目标准 |
| pyright | 快 | 最高 | VS Code 集成 | 日常开发 |
| ruff | 极快 | 中 | 替代 flake8+isort | 快速检查 |
| pytype | 中 | 中 | Google 出品 | 推断类型 |
# 安装与使用
pip install mypy ruff
# mypy 静态类型检查
mypy src/ --strict
# ruff 代码检查 + 格式化
ruff check src/
ruff format src/
本章小结
| 知识点 | 要点 |
|---|---|
| 类型注解 | 函数签名、变量、泛型、Protocol |
| 可变陷阱 | 默认参数、深浅拷贝、字典遍历 |
| 精度陷阱 | 浮点用 isclose,货币用 Decimal |
| 闭包陷阱 | 循环 lambda 需 i=i 立即绑定 |
| 静态检查 | mypy(严格) / pyright(快) / ruff(全能) |
下一章:数据结构——列表、字典、集合,Python 最强大的武器。