首页 / 知识库 / 0基础入门-阅读资料 / 0基础-python入门到精通

第 18 章 类型系统与类型提示

18.1 渐进式类型系统理念

Python 的类型系统是渐进式(Gradual) 的:

  • 类型注解是可选的,不写也完全合法
  • 注解不影响运行时行为——Python 不会因为类型标注错误而拒绝运行
  • 真正的类型检查由外部工具(mypy、pyright)完成
# 无类型注解 — 完全合法
def add(a, b):
    return a + b

# 有类型注解 — 更清晰
def add(a: int, b: int) -> int:
    return a + b

# 类型注解是元数据,运行时可以访问
print(add.__annotations__)
# {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}

# 但不会强制执行
add("hello", " world")  # 运行正常,返回 "hello world"

18.2 基础类型注解

# 变量注解(Python 3.6+)
name: str = "Alice"
age: int = 30
height: float = 1.68
is_active: bool = True

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

# 容器类型(Python 3.9+ 可直接用内置类型)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 90, "Bob": 85}
coords: tuple[float, float] = (3.0, 4.0)
unique_ids: set[int] = {1, 2, 3}

# 变长元组
values: tuple[int, ...] = (1, 2, 3, 4, 5)

Optional 和 Union

# Python 3.10 之前
from typing import Optional, Union

def find_user(user_id: int) -> Optional[str]:
    """返回用户名,未找到返回 None"""
    # Optional[str] 等价于 Union[str, None]
    return None

def parse_id(value: Union[str, int]) -> int:
    return int(value)

# Python 3.10+ 使用 | 语法
def find_user(user_id: int) -> str | None:
    return None

def parse_id(value: str | int) -> int:
    return int(value)

常用类型

from typing import Any, NoReturn
from collections.abc import Callable, Iterable, Iterator, Sequence

# Any — 任何类型(等于没写)
def log(message: Any) -> None:
    print(message)

# NoReturn — 函数永远不会正常返回
def fatal_error(msg: str) -> NoReturn:
    raise SystemExit(msg)

# Callable — 可调用对象
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

# Iterable / Iterator / Sequence
def process(items: Iterable[str]) -> list[str]:
    return [item.upper() for item in items]

def first(seq: Sequence[int]) -> int:
    return seq[0]

18.3 TypeVar 与泛型 Generic

from typing import TypeVar

T = TypeVar("T")

# 泛型函数 — 输入和输出类型一致
def first(items: list[T]) -> T:
    return items[0]

result = first([1, 2, 3])      # 推断 T=int,返回 int
result = first(["a", "b"])     # 推断 T=str,返回 str

# 有约束的 TypeVar
Num = TypeVar("Num", int, float)

def add(a: Num, b: Num) -> Num:
    return a + b

# 有上界的 TypeVar
from typing import Hashable
H = TypeVar("H", bound=Hashable)

泛型类

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]
    
    def is_empty(self) -> bool:
        return len(self._items) == 0

# 使用
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
value: int = int_stack.pop()

str_stack: Stack[str] = Stack()
str_stack.push("hello")

Python 3.12+ 新语法

# 旧语法
T = TypeVar("T")
def first(items: list[T]) -> T: ...

# 新语法(Python 3.12+)
def first[T](items: list[T]) -> T: ...

class Stack[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()

18.4 Protocol 结构化子类型

Protocol 实现了鸭子类型的形式化,无需继承即可满足类型约束:

from typing import Protocol

class HasLength(Protocol):
    def __len__(self) -> int: ...

class HasName(Protocol):
    name: str

def print_length(obj: HasLength) -> None:
    print(f"长度: {len(obj)}")

print_length([1, 2, 3])       # 通过——list 有 __len__
print_length("hello")          # 通过——str 有 __len__
print_length({"a": 1})        # 通过——dict 有 __len__

class Dog:
    name: str
    def __init__(self, name: str):
        self.name = name

def greet(obj: HasName) -> str:
    return f"Hello, {obj.name}!"

greet(Dog("Buddy"))  # 通过——Dog 有 name 属性

复杂 Protocol

from typing import Protocol

class Comparable(Protocol):
    def __lt__(self, other: "Comparable") -> bool: ...
    def __le__(self, other: "Comparable") -> bool: ...

class Serializable(Protocol):
    def to_dict(self) -> dict: ...
    
    @classmethod
    def from_dict(cls, data: dict) -> "Serializable": ...

class Repository(Protocol[T]):
    def get(self, id: int) -> T | None: ...
    def save(self, entity: T) -> None: ...
    def delete(self, id: int) -> bool: ...

18.5 Literal、Final、TypedDict

from typing import Literal, Final, TypedDict

# Literal — 限定字面量值
def set_direction(direction: Literal["north", "south", "east", "west"]) -> None:
    print(f"方向: {direction}")

set_direction("north")   # 通过
# set_direction("up")    # mypy 报错

# Final — 不可重新赋值的常量
MAX_SIZE: Final = 100
# MAX_SIZE = 200        # mypy 报错

class Config:
    DEBUG: Final[bool] = False

# TypedDict — 带类型的字典
class UserDict(TypedDict):
    name: str
    age: int
    email: str | None

# 可选键
class UserDict(TypedDict, total=False):
    name: str        # 必选(需要 Required)
    age: int         # 可选
    email: str       # 可选

from typing import Required, NotRequired

class UserDict(TypedDict):
    name: Required[str]
    age: NotRequired[int]
    email: NotRequired[str]

user: UserDict = {"name": "Alice"}  # 通过

18.6 ParamSpec 与 Concatenate

为装饰器编写精确的类型注解:

from typing import ParamSpec, Callable, TypeVar
from functools import wraps

P = ParamSpec("P")
R = TypeVar("R")

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

@log_call
def add(a: int, b: int) -> int:
    return a + b

# 类型检查器知道 add 仍然接受 (int, int) -> int
result: int = add(1, 2)
from typing import Concatenate

# 装饰器添加额外参数
def with_user(
    func: Callable[Concatenate[str, P], R]
) -> Callable[P, R]:
    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        return func("default_user", *args, **kwargs)
    return wrapper

@with_user
def greet(user: str, message: str) -> str:
    return f"{user}: {message}"

greet("hello")  # 自动注入 user 参数

18.7 TypeGuard 与类型窄化

from typing import TypeGuard

def is_string_list(val: list[object]) -> TypeGuard[list[str]]:
    """类型守卫:检查是否为字符串列表"""
    return all(isinstance(x, str) for x in val)

def process(items: list[object]) -> None:
    if is_string_list(items):
        # 这里 items 的类型被窄化为 list[str]
        for item in items:
            print(item.upper())  # mypy 知道 item 是 str
# isinstance 自动窄化类型
def handle(value: str | int) -> None:
    if isinstance(value, str):
        print(value.upper())    # mypy 知道是 str
    else:
        print(value + 1)        # mypy 知道是 int

# assert 也能窄化
def process(x: str | None) -> str:
    assert x is not None
    return x.upper()  # mypy 知道 x 不是 None

18.8 type 语句与类型别名(Python 3.12+)

# 旧方式 — 用赋值创建类型别名
from typing import TypeAlias

Vector: TypeAlias = list[float]
Matrix: TypeAlias = list[list[float]]
UserID: TypeAlias = int | str

# 新方式 — type 语句(Python 3.12+)
type Vector = list[float]
type Matrix = list[list[float]]
type UserID = int | str

# 泛型类型别名
type ListOrSet[T] = list[T] | set[T]

def process(data: ListOrSet[int]) -> int:
    return sum(data)

18.9 使用 mypy / pyright 进行静态检查

mypy

pip install mypy
mypy your_script.py
mypy --strict your_script.py  # 严格模式
# example.py
def greet(name: str) -> str:
    return "Hello, " + name

greet(42)  # mypy: Argument 1 to "greet" has incompatible type "int"; expected "str"

pyright

pip install pyright
pyright your_script.py

配置文件

# pyproject.toml
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
disallow_untyped_defs = true

[tool.pyright]
pythonVersion = "3.12"
typeCheckingMode = "strict"

处理无法标注的情况

from typing import cast, TYPE_CHECKING

# cast — 告诉类型检查器"相信我"
value = cast(int, some_unknown_value)

# type: ignore — 跳过该行检查
result = messy_function()  # type: ignore

# TYPE_CHECKING — 只在类型检查时为 True,运行时为 False
if TYPE_CHECKING:
    from heavy_module import HeavyClass

def func(obj: "HeavyClass") -> None:  # 字符串形式的前向引用
    pass

本章小结:Python 的类型系统是渐进式的——你可以逐步为代码添加类型注解。掌握泛型(TypeVar/Generic)、Protocol(结构化子类型)、以及装饰器类型标注(ParamSpec)是高级用法的关键。配合 mypy 或 pyright,类型提示能在运行前捕获大量 bug。