Python typing 模块常用类型提示工具

Python typing 模块常用类型提示工具

  • 每个工具都补充 典型使用场景优势 / 劣势
  • 所有示例均带 中文注释可直接运行(Python 3.10+,如用 3.8/3.9 需把 | 换成 Union)。

1. 基本标量类型(int、str …)

def add(a: int, b: int) -> int:
    """
    场景:业务逻辑里最常见的数值运算。
    优势:零成本、IDE 可推断、静态分析器 100% 支持。
    劣势:无法表达“正整数”“非空字符串”等更细粒度约束。
    """
    return a + b

2. 泛型容器 list / dict / set / tuple

from typing import Dict

def freq(words: list[str]) -> Dict[str, int]:
    """
    场景:处理同构集合(元素类型一致)。
    优势:写法直观;mypy/pyright 能发现“把 int 放进 list[str]”这类错误。
    劣势:运行时仍是原生容器,不检查具体元素;无法限制 list 长度。
    """
    counts: Dict[str, int] = {}
    for w in words:
        counts[w] = counts.get(w, 0) + 1
    return counts

3. Union 与 3.10+ |

from typing import Union   # 3.10 起可写 int | float

def parse_number(text: str) -> int | float:
    """
    场景:函数可能返回多种类型,调用方必须“类型收窄”。
    优势:静态检查可提醒“你忘了处理 float 分支”。
    劣势:返回值变“大 Union”,调用者需 isinstance 判断,易遗漏。
    """
    try:
        return int(text)
    except ValueError:
        return float(text)

4. Optional[T](Union[T, None] 的语法糖)

from typing import Optional

def find_user(uid: str) -> Optional[User]:
    """
    场景:查询类函数“找不到”时返回 None。
    优势:显式告诉调用者要做空值检查,减少 NoneType 错误。
    劣势:容易写出“Optional[Optional[T]]”这种套娃;None 语义弱,无法区分“不存在”还是“空结果”。
    """
    ...

5. TypeVar(泛型函数 / 类)

from typing import TypeVar, Sequence

T = TypeVar("T")          # 无界泛型
S = TypeVar("S", int, str)  # 限定为 int 或 str

def first(items: Sequence[T]) -> T | None:
    """
    场景:编写与元素类型无关的通用算法(如 first、last、chunk)。
    优势:保持 API 类型安全,调用方拿到精确类型。
    劣势:过度泛型会降低可读性;协变/逆变规则对初学者不友好。
    """
    return items[0] if items else None

6. Callable(高阶函数 / 回调)

from typing import Callable

def retry(
    fn: Callable[[str], bool],
    times: int = 3
) -> bool:
    """
    场景:装饰器、事件总线、策略模式。
    优势:明确参数 & 返回值;IDE 可给出补全和跳转。
    劣势:无法描述“关键字参数”、“可变参数”签名;复杂签名可读性差。
    """
    for _ in range(times):
        if fn("ping"):
            return True
    return False

7. Protocol(结构化子类型 / 鸭子类型)

from typing import Protocol, Iterator

class Readable(Protocol):
    def read(self, size: int = -1) -> bytes: ...

def stream_hash(reader: Readable) -> str:
    """
    场景:想接受“任何有 read() 方法的对象”,但不想继承公共基类。
    优势:解耦、符合鸭子类型;支持静态检查。
    劣势:Protocol 匹配基于“结构”,运行时仍可能因缺少属性抛错;多重继承冲突时调试困难。
    """
    hasher = hashlib.sha256()
    for chunk in iter(lambda: reader.read(8192), b""):
        hasher.update(chunk)
    return hasher.hexdigest()

8. TypedDict(半结构化 JSON / dict)

from typing import TypedDict, Required, NotRequired

class Movie(TypedDict, total=False):
    name: Required[str]
    year: int
    tags: list[str]

def to_xml(m: Movie) -> str:
    """
    场景:处理外部 JSON、ORM 字典行、配置文件。
    优势:字段级类型提示;配合 mypy --strict 可检测多余/缺失键。
    劣势:仍是 dict,运行时无强制校验;字段可选/必选、只读等高级标记语法冗长。
    """
    ...

9. NewType(轻量级“品牌类型”)

from typing import NewType

UserId = NewType("UserId", int)   # 运行时仍是 int,静态上视为不同类型
OrderId = NewType("OrderId", int)

def cancel_order(uid: UserId, oid: OrderId) -> None:
    """
    场景:防止“把用户 ID 当订单 ID”这种语义混淆。
    优势:零运行时开销;静态检查可发现混用。
    劣势:运行时无法区分,序列化/调试时打印仍是裸 int;需要额外适配 ORM / JSON。
    """
    ...

10. Final(常量、禁止继承 / 重写)

from typing import Final

MAX_RETRY: Final[int] = 3

class Config:
    DEBUG: Final[bool] = False

class Bad(Config):
    DEBUG = True  # mypy: Cannot assign to final name "DEBUG"

场景:定义全局常量、防止子类意外覆盖。
优势:静态检查 + 运行时 typing.final 装饰器双重保险。
劣势:仅提示,无法真正“锁定”值;与元编程(动态赋值)冲突。


11. Literal(枚举式取值)

from typing import Literal

LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR"]

def setup_logger(level: LogLevel) -> None:
    """
    场景:函数只接受有限的字符串/整数字面量。
    优势:比 Enum 轻量;静态检查可发现拼写错误。
    劣势:运行时仍是普通字符串;不能附加额外元数据(描述、值映射)。
    """
    ...

12. TypeGuard(自定义类型收窄函数)

from typing import TypeGuard

def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
    """
    场景:自己写 isinstance 逻辑,让 mypy/pyright 理解“已收窄”。
    优势:消除大量显式 cast;逻辑集中一处。
    劣势:只在静态检查有效,运行时仍是普通函数;复杂判断可能降低性能。
    """
    return all(isinstance(x, str) for x in val)

def concat(data: list[object]) -> str:
    if is_str_list(data):
        # mypy 在此分支把 data 视为 list[str]
        return "".join(data)
    raise ValueError("not all strings")

13. Annotated(把元数据附着在类型上)

from typing import Annotated
from fastapi import Query

def search(
    q: Annotated[str, Query(min_length=3, max_length=50)]
) -> list[str]:
    """
    场景:FastAPI/Pydantic/数据库驱动读取字段元数据。
    优势:不破坏类型本身,仅附加约束;工具链可统一消费。
    劣势:标准库无运行时校验,需要额外库解析;IDE 提示信息可能过长。
    """
    ...

14. TypeAlias / TypeAliasType(3.12+)

from typing import TypeAlias

# 旧写法
Vector: TypeAlias = tuple[float, ...]

# 3.12+ 新写法
type Matrix = list[Vector]

def scale(m: Matrix, k: float) -> Matrix:
    """
    场景:复杂嵌套类型需要可读名字。
    优势:IDE 跳转、文档化更清晰;3.12 起支持泛型 TypeAliasType。
    劣势:旧工具链可能未支持新语法。
    """
    return [[k * x for x in row] for row in m]

15. Any / NoReturn / Never

from typing import Any, NoReturn

def log(obj: Any) -> None:
    """
    场景:与动态语言交互、迁移旧代码、调试打印。
    优势:快速关闭类型检查,减少红色波浪线。
    劣势:一旦滥用,静态检查形同虚设;重构时无法获得帮助。
    """

def abort(msg: str) -> NoReturn:
    """
    场景:永远抛异常 / 退出进程。
    优势:告诉类型检查器“后续代码不可达”,避免“可能未初始化”误报。
    劣势:仅静态信息,运行时仍需手动 raise/exit。
    """
    raise SystemExit(msg)

小结:如何选型

需求 首选 慎用
同构集合 list[int] List(3.8-)
可选值 T | None 多层 Optional
鸭子类型 Protocol Any
结构字典 TypedDict dict[str, Any]
语义区分 NewType 裸 int/str
有限取值 Literal 字符串魔法值
元数据 Annotated 注释 + 运行时手动解析

把这张表与示例代码放进团队 Wiki,就能让类型提示既“写得爽”又“跑得稳”。

posted @ 2025-08-20 15:44  luke0366  阅读(48)  评论(0)    收藏  举报