跳转至

Python @overload 装饰器约束函数签名

Python 中的 @overload 装饰器用于向类型检查器声明:同一函数拥有不同的参数和返回值类型组合。其核心作用是让类型检查器能够根据调用时的实际参数值,推断出更精确的返回类型,从而避免不必要的类型断言或空值检查。它仅作用于静态类型分析阶段,不影响运行时行为。

本文介绍了 @overload 的使用动机、正确写法及类型检查效果对比。

问题场景

考虑一个用户查询函数:根据传入的参数类型决定返回类型。传入单个 int ID 时返回单个 User 对象;传入 list[int] ID 列表时返回 list[User] 对象列表。

不使用 @overload 的写法

Python
class User:
    def __init__(self, id: int, name: str) -> None:
        self.id = id
        self.name = name


USERS = {1: User(1, "Alice"), 2: User(2, "Bob"), 3: User(3, "Carol")}


def fetch_user(id_or_ids: int | list[int]) -> User | list[User]:
    """Fetch user(s) by ID, supports single or batch query."""
    if isinstance(id_or_ids, int):
        return USERS[id_or_ids]
    return [USERS[i] for i in id_or_ids]

类型检查器输出

1768319145341

问题:即使调用方明确传入 int,类型检查器仍认为返回值可能是 list[User],导致后续代码需要冗余的 isinstance 检查。


使用 @overload 的正确方式

Python
from typing import overload


class User:
    def __init__(self, id: int, name: str) -> None:
        self.id = id
        self.name = name


USERS = {1: User(1, "Alice"), 2: User(2, "Bob"), 3: User(3, "Carol")}


@overload
def fetch_user(id_or_ids: int) -> User:
    ...


@overload
def fetch_user(id_or_ids: list[int]) -> list[User]:
    ...


def fetch_user(id_or_ids: int | list[int]) -> User | list[User]:
    """Fetch user(s) by ID, supports single or batch query."""
    if isinstance(id_or_ids, int):
        return USERS[id_or_ids]
    return [USERS[i] for i in id_or_ids]

类型检查器输出

Python
# Case 1: Pass a single ID
user = fetch_user(1)
user.name

# Case 2: Pass a list of IDs
users = fetch_user([1, 2, 3])
users[0].name  # OK

1768319302474


使用方法

规则 说明
@overload 函数体必须是 ...pass 这些签名仅供类型检查器读取,运行时不执行
真实实现必须放在最后且不加 @overload 运行时只有这个实现会被调用
真实实现的签名必须兼容所有 overload 签名 类型检查器会验证一致性
使用 Literal 精确约束参数值 @overload def func(x: Literal[True]) -> str: ...
@overload def func(x: Literal[False]) -> float: ...

总结

该用:函数返回类型与特定参数值存在确定性映射关系,且调用方需要在类型层面区分这些情况。

不该用:返回类型仅与参数类型相关(这可以用泛型 TypeVar 解决),或返回类型固定不变(直接标注即可)。

评论