Skip to content

类型系统与 typing

现代 Python 开发必须掌握类型注解。它让代码更可读、IDE 更智能、bug 更早暴露。

为什么需要类型注解

python
# 没有类型注解 — 参数含义不明
def process(data, config, flag):
    ...

# 有类型注解 — 一目了然
def process(
    data: list[dict],
    config: dict[str, str],
    flag: bool = False
) -> list[str]:
    ...

基础类型注解

python
# 变量注解
name: str = "Alice"
age: int = 30
score: float = 9.5
active: bool = True

# 函数注解
def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()

# 注解不影响运行时(Python 是动态语言)
greet(123, "abc")  # 运行时不报错,但 mypy 会报错

typing 模块核心类型

容器类型

python
from typing import List, Dict, Tuple, Set  # Python 3.8 及以下
# Python 3.9+ 直接用内置类型
from __future__ import annotations  # 3.7-3.9 启用新语法

def process_users(
    names: list[str],
    scores: dict[str, float],
    coords: tuple[int, int],
    tags: set[str],
) -> list[dict[str, float]]:
    ...

OptionalUnion

python
from typing import Optional, Union

# Optional[X] 等价于 Union[X, None]
def find_user(user_id: int) -> Optional[dict]:
    ...  # 可能返回 None

# Union — 多种类型之一
def parse(value: Union[str, int, float]) -> str:
    return str(value)

# Python 3.10+ 用 | 语法
def parse_new(value: str | int | float) -> str:
    return str(value)

Any — 逃生舱

python
from typing import Any

def legacy_function(data: Any) -> Any:
    # 与旧代码交互时使用,尽量避免
    return data

泛型

python
from typing import TypeVar, Generic

T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

    def peek(self) -> T:
        return self._items[-1]

# 使用时类型安全
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
val: int = int_stack.pop()  # mypy 知道这是 int

Protocol — 结构子类型

不需要继承,只需要有对应的方法:

python
from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...
    def resize(self, factor: float) -> None: ...

class Circle:
    def draw(self) -> None:
        print("画圆")
    def resize(self, factor: float) -> None:
        self.radius *= factor

class Square:
    def draw(self) -> None:
        print("画方")
    def resize(self, factor: float) -> None:
        self.side *= factor

def render(shape: Drawable) -> None:
    shape.draw()

# Circle 和 Square 都没有继承 Drawable,但满足协议
render(Circle())  # 类型检查通过
render(Square())  # 类型检查通过

TypedDict — 字典类型

python
from typing import TypedDict, Required, NotRequired

class UserProfile(TypedDict):
    name: str
    age: int
    email: str

# Python 3.11+ 支持 Required/NotRequired
class Config(TypedDict):
    host: Required[str]
    port: Required[int]
    debug: NotRequired[bool]  # 可选字段

def create_user(profile: UserProfile) -> None:
    print(f"创建用户: {profile['name']}")

# mypy 会检查字段类型和必填项
create_user({"name": "Alice", "age": 30, "email": "alice@example.com"})

Literal — 字面量类型

python
from typing import Literal

Mode = Literal['r', 'w', 'a', 'rb', 'wb']

def open_file(path: str, mode: Mode) -> None:
    ...

open_file("data.txt", "r")    # OK
# open_file("data.txt", "x")  # mypy 报错:不在 Literal 范围内

dataclass + 类型注解

python
from dataclasses import dataclass, field
from typing import ClassVar

@dataclass
class Point:
    x: float
    y: float
    label: str = ""
    _cache: dict = field(default_factory=dict, repr=False)
    
    # 类变量(不是实例字段)
    dimensions: ClassVar[int] = 2

    def distance_to(self, other: 'Point') -> float:
        return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5

p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance_to(p2))  # 5.0

ParamSpecConcatenate — 装饰器类型

python
from typing import ParamSpec, Callable, TypeVar

P = ParamSpec('P')
R = TypeVar('R')

def logged(func: Callable[P, R]) -> Callable[P, R]:
    import functools
    @functools.wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        print(f"调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@logged
def add(x: int, y: int) -> int:
    return x + y

# mypy 知道 add 的参数类型仍然是 (int, int) -> int
result: int = add(1, 2)

overload — 函数重载

python
from typing import overload

@overload
def process(x: int) -> int: ...
@overload
def process(x: str) -> str: ...
@overload
def process(x: list) -> list: ...

def process(x):
    if isinstance(x, int):
        return x * 2
    elif isinstance(x, str):
        return x.upper()
    else:
        return [i * 2 for i in x]

# mypy 根据输入类型推断返回类型
n: int = process(5)
s: str = process("hello")

运行时类型检查

python
from typing import get_type_hints
import inspect

def validate_call(func):
    hints = get_type_hints(func)
    sig = inspect.signature(func)
    
    import functools
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        bound = sig.bind(*args, **kwargs)
        for name, value in bound.arguments.items():
            if name in hints and not isinstance(value, hints[name]):
                raise TypeError(f"{name} 应为 {hints[name]}")
        return func(*args, **kwargs)
    return wrapper

# 或者直接用 pydantic 的 @validate_call(更强大)
from pydantic import validate_call

@validate_call
def create_user(name: str, age: int) -> dict:
    return {"name": name, "age": age}

create_user("Alice", 30)    # OK
# create_user("Alice", "30") # ValidationError(pydantic 会自动转换或报错)

推荐工具

  • mypy:最成熟的静态类型检查器
  • pyright / pylance:微软出品,VS Code 内置,速度更快
  • pydantic:运行时类型验证,FastAPI 的基础

本站内容由 褚成志 整理编写,仅供学习参考