文章目录
-
- 从「注解只是提示」到「类型系统是工程工具」
- `Union`:一个值,多种可能类型
- `Optional`:可能为空的值
-
- [本质:`Optional[X]` 就是 `Union[X, None]`](#本质:
Optional[X]就是Union[X, None]) - [为什么需要 `Optional` 而不是直接写 `None`](#为什么需要
Optional而不是直接写None) - [Optional 的常见误用](#Optional 的常见误用)
- [Python 3.10+ 的 `| None` vs `Optional`](#Python 3.10+ 的
| NonevsOptional)
- [本质:`Optional[X]` 就是 `Union[X, None]`](#本质:
- `Any`:类型系统的逃生舱
-
- [Any 的语义:放弃类型检查](#Any 的语义:放弃类型检查)
- [Any 的传播性](#Any 的传播性)
- [Any 的合理使用场景](#Any 的合理使用场景)
- `cast`:运行时不生效的类型断言
- `Callable`:函数也是类型
- 四者的关系:类型系统的层次
- [工程实战:API 响应处理的类型安全方案](#工程实战:API 响应处理的类型安全方案)
- [类型注解的运行时影响:`get_type_hints` 与 `annotations`](#类型注解的运行时影响:
get_type_hints与__annotations__) -
- [`from future import annotations` 的影响](#
from __future__ import annotations的影响)
- [`from future import annotations` 的影响](#
- [mypy 配置与常用检查项](#mypy 配置与常用检查项)
-
- [mypy 的严格级别](#mypy 的严格级别)
- [常见 mypy 报错与修复](#常见 mypy 报错与修复)
- 类型注解的性能考量
-
- [`get_type_hints()` 的性能](#
get_type_hints()的性能) - [Pydantic 与 `model_rebuild`](#Pydantic 与
model_rebuild)
- [`get_type_hints()` 的性能](#
- 选型决策树:什么时候用什么
- 最佳实践总结
-
- 类型注解的渐进式策略
- 反模式清单
- [mypy 推荐配置](#mypy 推荐配置)
- 小结
Python 的类型注解不是为了把 Python 变成 Java------而是为了在保持动态灵活性的同时,获得静态检查的安全网。
从「注解只是提示」到「类型系统是工程工具」
在 Python 数据模型:双下划线方法的全景图 中,曾提到 Python 的类型系统是"可选的、渐进的"------不注解也能跑,注解了也不影响运行时行为。但"不影响运行时"并不意味着"没有价值"。
类型注解的核心价值在于静态分析阶段捕获错误:
python
def greet(name: str) -> str:
return f"Hello, {name}"
greet(42) # mypy 报错: Argument 1 to "greet" has incompatible type "int"; expected "str"
运行时 greet(42) 不会报错(f"Hello, {42}" 完全合法),但语义上这是一个 bug------函数期望的是人名,传入的是数字。mypy 在代码执行之前就能发现这个问题。
这就是类型注解的工程定位:不改变运行时,但改变开发时的信心。
本文将深入四个最核心的类型构造器------Union、Optional、Any 与 Callable------从语法到语义,从陷阱到最佳实践。
Union:一个值,多种可能类型
基本语法
Union[X, Y] 表示"这个值可以是 X 类型,也可以是 Y 类型":
python
from typing import Union
def process_id(user_id: Union[int, str]) -> str:
"""处理用户 ID,支持整数和字符串两种格式"""
if isinstance(user_id, int):
return f"UID-{user_id:06d}"
return f"UID-{user_id.strip()}"
print(process_id(42)) # UID-000042
print(process_id("admin")) # UID-admin
多类型联合
Union 可以包含任意数量的类型:
python
from typing import Union
JsonValue = Union[None, bool, int, float, str, list, dict]
def validate_json(value: JsonValue) -> bool:
"""验证 JSON 值的合法性"""
if isinstance(value, (list, dict)):
return all(validate_json(v) for v in value) if isinstance(value, list) else True
return isinstance(value, (type(None), bool, int, float, str))
Python 3.10+ 的新语法:X | Y
PEP 604 引入了更简洁的联合类型语法,不再需要导入 Union:
python
# Python 3.10+ 写法
def process_id(user_id: int | str) -> str:
if isinstance(user_id, int):
return f"UID-{user_id:06d}"
return f"UID-{user_id.strip()}"
# 多类型联合
JsonValue = None | bool | int | float | str | list | dict
| 语法与 Union 完全等价,且可以在 isinstance() 中使用:
python
# Python 3.10+ 的 isinstance 支持 | 语法
def handle(value: int | str | float) -> str:
if isinstance(value, int | float):
return f"numeric: {value}"
return f"string: {value}"
Union 的语义陷阱:不是"任意一个"
Union[int, str] 的语义是"值恰好是 int 或 str 其中之一",而不是"值可以随意在 int 和 str 之间转换":
python
from typing import Union
def add(left: Union[int, str], right: Union[int, str]) -> Union[int, str]:
"""危险:返回类型取决于输入组合,调用方无法安全使用"""
if isinstance(left, int) and isinstance(right, int):
return left + right # int + int → int
return f"{left}{right}" # 任意组合 → str
result = add(1, 2) # 返回 int
result = add("a", "b") # 返回 str
result = add(1, "b") # 返回 str------调用方拿到 Union[int, str],必须再判断
这种"输出类型取决于输入类型组合"的场景,正确的做法是使用函数重载 (@overload):
python
from typing import overload, Union
@overload
def add(left: int, right: int) -> int: ...
@overload
def add(left: str, right: str) -> str: ...
@overload
def add(left: int, right: str) -> str: ...
@overload
def add(left: str, right: int) -> str: ...
def add(left: Union[int, str], right: Union[int, str]) -> Union[int, str]:
if isinstance(left, int) and isinstance(right, int):
return left + right
return f"{left}{right}"
reveal_type(add(1, 2)) # mypy 推断为 int
reveal_type(add("a", "b")) # mypy 推断为 str
reveal_type(add(1, "b")) # mypy 推断为 str
原则 :当函数的返回类型依赖于多个参数的组合时,用
@overload提供精确的类型签名,而不是让所有路径都返回Union。
Union 缩化(Narrowing)
mypy 支持通过 isinstance、type() 检查自动缩化 Union 类型:
python
from typing import Union
def process(value: Union[int, str]) -> int:
# 此处 value 的类型是 Union[int, str]
if isinstance(value, int):
# mypy 知道此处 value 一定是 int
return value ** 2
# mypy 知道此处 value 一定是 str(Union 的剩余分支)
return len(value)
类型缩化是类型安全的基石------永远不要用 type: ignore 跳过缩化,而是用 isinstance 让 mypy 自动推断。
Optional:可能为空的值
本质:Optional[X] 就是 Union[X, None]
python
from typing import Optional
# 这两种写法完全等价
def find_user(name: str) -> Optional[str]:
...
def find_user(name: str) -> Union[str, None]:
...
Python 3.10+ 的等价写法:
python
def find_user(name: str) -> str | None:
...
为什么需要 Optional 而不是直接写 None
语义清晰度。Optional[str] 传达的是"这个位置可能没有值",而 Union[str, None] 传达的是"这个值可以是字符串或 None"。虽然类型等价,但语义重心不同:
Optional强调"可选性"------参数或返回值可以缺失Union强调"多态性"------值可以是多种类型之一
python
from typing import Optional
# 场景一:可选参数------强调"可选性",用 Optional
def connect(host: str, port: Optional[int] = None) -> None:
"""port 不传则使用默认端口"""
actual_port = port if port is not None else 3306
print(f"Connecting to {host}:{actual_port}")
# 场景二:查找函数------返回值可能不存在,用 Optional
def find_config(key: str) -> Optional[str]:
"""查找配置项,可能不存在"""
config = {"host": "localhost", "port": "3306"}
return config.get(key) # dict.get 默认返回 None
result = find_config("timeout")
if result is not None:
print(result.upper()) # mypy 确认此处 result 是 str
Optional 的常见误用
误用一:用 Optional 包裹容器类型
python
# ❌ 错误:Optional[list] 暗示"可能是 list,也可能是 None"
# 但实际上"空列表"和 None 的语义完全不同
def get_items(user_id: int) -> Optional[list]:
...
# ✅ 正确:返回空列表表示"没有元素",用 None 表示"查询失败"
def get_items(user_id: int) -> list:
"""返回用户的条目列表,没有条目时返回空列表"""
return []
def find_items(user_id: int) -> Optional[list]:
"""查找用户的条目列表,用户不存在时返回 None"""
...
误用二:不处理 None 就直接使用
python
from typing import Optional
def get_name() -> Optional[str]:
return None
# ❌ mypy 报错: Item "None" of "Optional[str]" has no attribute "upper"
name = get_name()
print(name.upper()) # 运行时 AttributeError: 'NoneType' object has no attribute 'upper'
# ✅ 先判断 None
name = get_name()
if name is not None:
print(name.upper())
Python 3.10+ 的 | None vs Optional
python
# 传统写法
from typing import Optional
def find(id: int) -> Optional[str]: ...
# 3.10+ 写法(推荐,更简洁)
def find(id: int) -> str | None: ...
两者完全等价,| None 是 PEP 604 的一部分,与 Union 的 | 语法统一。对于新项目,推荐使用 | None。
Any:类型系统的逃生舱
Any 的语义:放弃类型检查
Any 是类型系统中最"危险"的类型------它告诉 mypy:"不要检查这个值,任何操作都可以"。
python
from typing import Any
def process(data: Any) -> Any:
# mypy 不会报任何错------因为 Any 兼容一切类型
result = data.upper() # OK(即使 data 可能是 int)
result = data + 1 # OK(即使 data 可能是 str)
result = data.nonexistent() # OK(即使方法不存在)
return result
process(42) # 运行时 AttributeError: 'int' object has no attribute 'upper'
process("hello") # 运行时 TypeError: can only concatenate str (not "int") to str
Any 的传播性
Any 具有传染性 ------一旦一个值被推断为 Any,所有与之交互的值也会变成 Any:
python
from typing import Any
def untyped_function(x: Any) -> None:
y = x + 1 # y 的类型被推断为 Any
z = y.upper() # z 的类型被推断为 Any
print(z) # 整个调用链都是 Any
# 从 Any 传播到外部
result: Any = some_untyped_library()
name: str = result.name # name 的类型标注是 str,但实际值可能是 Any
length = len(name) # length 被推断为 int,但如果 name 不是 str......
这就像 Rust 的 unsafe 块------一旦进入 Any 的世界,类型安全不再有保证,必须手动恢复。
Any 的合理使用场景
尽管危险,Any 在以下场景是必要的:
场景一:与无类型注解的第三方库交互
python
from typing import Any
import legacy_module # 没有 type stub 的老库
def safe_wrapper(data: dict[str, Any]) -> str:
"""包装无类型库调用,在边界处收缩类型"""
# legacy_module.process 返回 Any
raw_result: Any = legacy_module.process(data)
# 在边界处做运行时校验,恢复类型安全
if isinstance(raw_result, str):
return raw_result
raise TypeError(f"Expected str, got {type(raw_result)}")
场景二:类型注解还不支持的表达
python
from typing import Any
# 自序列化场景:JSON 值的类型在运行时才能确定
def parse_value(raw: str) -> Any:
"""解析 JSON 值,类型在运行时决定"""
import json
return json.loads(raw) # 可能是 int、str、list、dict......
# 正确做法:用 Union 替代 Any 表示"已知的多种类型"
JsonValue = None | bool | int | float | str | list["JsonValue"] | dict[str, "JsonValue"]
def parse_value_typed(raw: str) -> JsonValue:
import json
return json.loads(raw)
场景三:object 与 Any 的区别
python
def use_object(x: object) -> None:
# object 是所有类型的父类,但只能调用 object 的方法
print(hash(x)) # OK------hash 是 object 的方法
print(x.upper()) # ❌ mypy 报错------object 没有 upper 方法
def use_any(x: Any) -> None:
# Any 允许任意操作,不做任何检查
print(x.upper()) # OK(mypy 不报错)
print(x + 1) # OK(mypy 不报错)
原则 :如果只需要表示"任意类型",优先用
object;只有在需要绕过类型检查时才用Any。
cast:运行时不生效的类型断言
当开发者比 mypy 更了解类型时,使用 cast 而不是 Any:
python
from typing import cast
def get_config() -> dict[str, int]:
"""从外部源获取配置,开发者知道值都是 int"""
raw: dict[str, object] = load_raw_config() # 返回类型不够精确
# cast 只在类型检查时生效,运行时是空操作
return cast(dict[str, int], raw)
# ❌ 危险的替代方案:用 Any 绕过
def get_config_unsafe() -> dict[str, int]:
raw: dict[str, Any] = load_raw_config()
return raw # mypy 不报错,但类型安全完全丧失
cast 和 Any 的关键区别:
cast是局部的、一次性的类型断言,影响范围有限Any是全局的、传染性的,会沿调用链传播
Callable:函数也是类型
基本语法
Callable[[Arg1Type, Arg2Type], ReturnType] 表示一个可调用对象的类型签名:
python
from typing import Callable
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
"""将一个二元函数应用到两个整数上"""
return func(a, b)
def add(x: int, y: int) -> int:
return x + y
def multiply(x: int, y: int) -> int:
return x * y
print(apply(add, 3, 4)) # 7
print(apply(multiply, 3, 4)) # 12
回调函数场景
Callable 最常见的用途是类型化回调函数和策略模式:
python
from typing import Callable
# 策略模式:不同的折扣策略
DiscountStrategy = Callable[[float], float]
def no_discount(price: float) -> float:
return price
def percentage_discount(price: float, rate: float = 0.1) -> float:
return price * (1 - rate)
def fixed_discount(price: float, amount: float = 100) -> float:
return max(0, price - amount)
class Order:
def __init__(self, total: float, discount_fn: DiscountStrategy) -> None:
self.total = total
self.discount_fn = discount_fn
def final_price(self) -> float:
return self.discount_fn(self.total)
# 使用
order1 = Order(1000, no_discount)
order2 = Order(1000, lambda p: percentage_discount(p, 0.2))
print(order1.final_price()) # 1000.0
print(order2.final_price()) # 800.0
无参数可调用:Callable[[], T]
python
from typing import Callable
def lazy_init(factory: Callable[[], str]) -> str:
"""延迟初始化:只在需要时调用工厂函数"""
return factory()
# 无参数的工厂函数
result = lazy_init(lambda: "initialized")
print(result) # initialized
可调用但不关心签名:Callable[..., T]
...(Ellipsis)表示"接受任意参数":
python
from typing import Callable
def log_and_call(func: Callable[..., int], *args: object, **kwargs: object) -> int:
"""调用任意函数并记录日志,不关心参数签名"""
print(f"Calling {func.__name__}")
return func(*args, **kwargs) # type: ignore[arg-type]
def compute(a: int, b: int) -> int:
return a + b
print(log_and_call(compute, 1, 2)) # Calling compute → 3
注意 :
Callable[..., T]放弃了参数类型的检查,应尽量少用。如果参数可变,优先使用Protocol定义可调用协议(后续文章会展开)。
Python 3.10+:Callable 的局限与替代
Callable 有一个根本限制------只能描述位置参数,无法表达关键字参数、默认值、*args、**kwargs:
python
from typing import Callable
# ❌ Callable 无法表达"第二个参数有默认值"
def register(callback: Callable[[str, int], None]) -> None:
...
# 实际上 callback 可能是 def on_event(name: str, priority: int = 0): ...
# 但 Callable 无法编码这个信息
Python 3.12+ 的 PEP 695 提供了更强大的语法(后文会涉及),但在此之前,需要用 Protocol 来描述复杂的可调用签名:
python
from typing import Protocol
class EventHandler(Protocol):
def __call__(self, name: str, priority: int = 0) -> None: ...
def register(handler: EventHandler) -> None:
handler("click") # OK,priority 使用默认值
handler("click", priority=1) # OK
四者的关系:类型系统的层次
具体类型
str, int, list[int]
Union[X, Y]
多选一
Optional[X]
Union[X, None] 的语法糖
Callable[[Args], R]
函数签名类型
object
所有类型的父类
只能调用 object 方法
Any
类型系统的逃生舱
兼容一切操作
从具体到抽象的谱系:
| 类型构造器 | 语义 | 类型检查行为 | 适用场景 |
|---|---|---|---|
具体类型 int |
值恰好是这个类型 | 完全检查 | 参数类型明确 |
Union[X, Y] |
值是 X 或 Y | 检查 + 缩化 | 参数接受多种类型 |
Optional[X] |
值是 X 或 None | 检查 + None 判断 | 可选参数、可能缺失的返回值 |
Callable[[A], R] |
函数签名 | 检查参数和返回类型 | 回调、策略模式 |
object |
任意值(只读) | 只允许 object 方法 | 不关心具体类型、仅需哈希/打印 |
Any |
任意值(读写) | 完全不检查 | 与无类型库交互、类型系统边界 |
工程实战:API 响应处理的类型安全方案
将四个类型构造器组合在一个真实的 API 响应处理场景中:
python
"""API 响应处理器:演示 Union/Optional/Any/Callable 的工程化组合"""
from typing import Any, Callable, Optional, Union
import json
# ========== 第一层:原始数据 → 类型化数据 ==========
# JSON 响应可能是多种结构
ApiResponse = Union[
dict[str, Any], # 成功响应
str, # 错误消息
None, # 超时或无响应
]
def parse_response(raw: str) -> ApiResponse:
"""解析原始 API 响应,区分三种情况"""
if not raw:
return None # 空响应 → None
try:
data = json.loads(raw)
if isinstance(data, dict):
return data
return str(data) # 非 dict 响应视为错误消息
except json.JSONDecodeError:
return raw # JSON 解析失败,返回原始字符串
# ========== 第二层:类型缩化 + 安全访问 ==========
def extract_field(
response: ApiResponse,
field: str,
) -> Optional[str]:
"""从 API 响应中安全提取字段"""
# 类型缩化:排除 None 和 str 分支
if response is None:
return None
if isinstance(response, str):
return None # 错误消息中没有字段
# 此处 response 一定是 dict[str, Any]
value: Any = response.get(field)
if isinstance(value, str):
return value
return None
# ========== 第三层:可配置的响应处理 ==========
# 处理器类型:接受响应,返回布尔表示是否成功
ResponseHandler = Callable[[ApiResponse], bool]
def success_handler(response: ApiResponse) -> bool:
"""成功处理器:检查响应是否包含数据"""
if isinstance(response, dict):
return "data" in response
return False
def retry_handler(response: ApiResponse) -> bool:
"""重试处理器:检查是否需要重试"""
if response is None:
return True # 超时需要重试
if isinstance(response, str) and "timeout" in response.lower():
return True
return False
def process_with_handler(
raw: str,
handler: ResponseHandler,
) -> bool:
"""用可配置的处理器处理 API 响应"""
response = parse_response(raw)
return handler(response)
# ========== 使用示例 ==========
# 场景一:正常响应
result = extract_field('{"name": "Alice", "age": 30}', "name")
print(f"Name: {result}") # Name: Alice
# 场景二:空响应
result = extract_field('', "name")
print(f"Name: {result}") # Name: None
# 场景三:成功检测
is_success = process_with_handler('{"data": [1, 2, 3]}', success_handler)
print(f"Success: {is_success}") # Success: True
# 场景四:重试检测
should_retry = process_with_handler('Connection timeout', retry_handler)
print(f"Should retry: {should_retry}") # Should retry: True
类型注解的运行时影响:get_type_hints 与 __annotations__
类型注解存储在 __annotations__ 字典中,可以通过 typing.get_type_hints() 获取解析后的类型:
python
from typing import Optional, get_type_hints, Union
def example(name: str, age: Optional[int] = None) -> Union[str, int]:
...
# 直接访问原始注解(字符串形式)
print(example.__annotations__)
# {'name': <class 'str'>, 'age': typing.Optional[int], 'return': typing.Union[str, int]}
# get_type_hints 解析前向引用和字符串注解
print(get_type_hints(example))
# {'name': <class 'str'>, 'age': typing.Union[int, NoneType], 'return': typing.Union[str, int]}
from __future__ import annotations 的影响
PEP 563(Python 3.7+,默认延迟求值注解)改变了注解的存储方式:
python
from __future__ import annotations
from typing import Optional
def example(name: str, age: Optional[int] = None) -> str | None:
...
# 所有注解都变成字符串,不再在定义时求值
print(example.__annotations__)
# {'name': 'str', 'age': 'Optional[int]', 'return': 'str | None'}
# get_type_hints 会尝试求值字符串
print(get_type_hints(example))
# 需要注解中的名称在模块命名空间中可用
注意 :
from __future__ import annotations让所有注解变成字符串,这可以避免前向引用问题,但也意味着注解在定义时不会被求值------如果注解中有拼写错误,只有调用get_type_hints()时才会发现。
mypy 配置与常用检查项
mypy 的严格级别
ini
# pyproject.toml 中的 mypy 配置
[tool.mypy]
python_version = "3.12"
strict = true
# 等价于启用以下所有选项:
# disallow_untyped_defs = true # 函数必须有类型注解
# disallow_any_generics = true # 泛型必须指定类型参数
# warn_return_any = true # 返回 Any 时警告
# disallow_untyped_calls = true # 调用无类型函数时报错
# warn_unused_ignores = true # 未使用的 type: ignore 报错
# no_implicit_optional = true # 禁止隐式 Optional
常见 mypy 报错与修复
python
# 报错一:Incompatible return type
def maybe_get() -> str:
if some_condition:
return "found"
return None # ❌ mypy: Incompatible return value type (got "None", expected "str")
# 修复:使用 Optional
def maybe_get() -> str | None:
if some_condition:
return "found"
return None
# 报错二:Item "None" has no attribute
def process(data: str | None) -> int:
return data.upper() # ❌ mypy: Item "None" of "Optional[str]" has no attribute "upper"
# 修复:先判断 None
def process(data: str | None) -> int:
if data is None:
return 0
return len(data.upper())
# 报错三:Implicit Optional(no_implicit_optional = true 时)
def greet(name: str = None) -> str: # ❌ mypy: Implicit Optional
return f"Hello, {name}"
# 修复:显式声明 Optional
def greet(name: str | None = None) -> str:
if name is None:
return "Hello, anonymous"
return f"Hello, {name}"
类型注解的性能考量
类型注解在运行时 的开销极小------CPython 在定义函数/类时把注解存入 __annotations__ 字典,之后不再访问。但在以下场景中需要注意:
get_type_hints() 的性能
python
from typing import get_type_hints
import time
class BigModel:
"""含 50 个字段的模型"""
field_00: str
field_01: int
# ... 省略 field_02 ~ field_48
field_49: bool
# get_type_hints 在每次调用时都重新求值
start = time.perf_counter()
for _ in range(10000):
get_type_hints(BigModel)
elapsed = time.perf_counter() - start
print(f"10000 calls to get_type_hints: {elapsed:.4f}s")
# 约 0.5~1.0s,因为每次都重新解析注解
Pydantic 与 model_rebuild
Pydantic v2 使用 Rust 核心,运行时类型验证非常快。但如果模型包含前向引用,需要在所有类定义完成后调用 model_rebuild():
python
from pydantic import BaseModel
class Node(BaseModel):
value: int
children: list["Node"] # 前向引用
Node.model_rebuild() # 解析前向引用,构建验证器
选型决策树:什么时候用什么
否
是
否
是
是
否
只读操作
需要任意操作
是
是
否
需要注解一个类型
值可能是
多种类型之一?
值可能为 None?
类型是否已知
且数量有限?
使用具体类型
str, int, list[int]
使用 Optional[X]
或 X | None
使用 Union[X, Y, Z]
或 X | Y | Z
是否完全不
关心类型?
使用 object
只允许 object 方法
使用 Any
并在边界收缩
值是可调用对象?
签名是否简单?
使用 Callable[[Args], R]
使用 Protocol
定义可调用协议
最佳实践总结
类型注解的渐进式策略
- 公共 API 优先:先为模块的公共函数、类方法添加注解,内部实现可以后补
- 返回值优先于参数:返回值类型决定了调用方的使用方式,优先注解
- 从窄到宽 :用
具体类型 → Optional → Union → object → Any的顺序,尽量使用最窄的类型 - 在边界收缩 Any :与外部库交互时,入口参数可以是
Any,但内部函数应立即转换为具体类型
反模式清单
| 反模式 | 问题 | 修复 |
|---|---|---|
def foo(x) 无注解 |
mypy 无法检查 | 至少加 x: object |
def foo(x: Any) 滥用 Any |
完全丧失类型安全 | 用具体类型或 Union |
Optional[list] 返回值 |
混淆空列表和 None | 空列表用 list,查询失败用 Optional |
Union 返回值不缩化 |
调用方必须判断 | 用 @overload 精确签名 |
cast(Any, x) 绕过检查 |
等于没有注解 | 用具体类型 cast(dict[str, int], x) |
Callable[..., Any] |
完全不检查 | 用 Protocol 定义签名 |
mypy 推荐配置
ini
[tool.mypy]
python_version = "3.12"
strict = true
warn_redundant_casts = true
warn_unused_ignores = true
disallow_any_generics = true
# 第三方库缺少 stub 时
[[tool.mypy.overrides]]
module = "legacy_module.*"
ignore_missing_imports = true
小结
| 构造器 | 一句话定位 | 核心风险 | 核心原则 |
|---|---|---|---|
Union |
多选一的类型联合 | 不缩化则调用方痛苦 | 搭配 isinstance 做类型缩化,复杂场景用 @overload |
Optional |
可能不存在的值 | 不判 None 就使用 | 永远在使用前检查 is not None,用 ` |
Any |
类型系统的逃生舱 | 传染性扩散 | 只在边界使用,进入内部立刻收缩为具体类型 |
Callable |
函数签名类型 | 无法表达默认值和 *args |
简单签名用 Callable,复杂签名用 Protocol |
类型注解不是"写对了就完事"------它是一个持续演进的工程实践。从具体类型出发,在需要时引入 Union 和 Optional,在边界处处理 Any,用 Callable 描述回调契约------这四者是类型注解工程化的基石。
如果这篇文章对理解 Python 类型注解有帮助,点赞收藏让更多人看到!关注专栏,持续获取 Python 进阶干货。