Vanson's Eternal Blog

Python中的Typing模块

Python typing.png
Published on
/15 mins read/---

typing 模块提供了许多高级特性,这些特性可以帮助开发者编写更健壮、更易维护的代码。 以下是一些高级用法的详细解析,结合了最新的 typing 模块功能和 typing-extensions 包的内容。

基础类型

Any:任意类型‌

表示变量可以是任何类型,‌完全禁用类型检查‌(慎用,优先用更具体的类型)。

from typing import Any
 
def print_anything(data: Any) -> None:
    print(data)  # 可以是 int、str、dict 等任意类型
 
print_anything(42)     # 合法
print_anything("hello")# 合法
 

Union:联合类型‌

表示变量可以是多个类型中的一种(Python 3.10+ 可用 | 替代)。

 
from typing import Union
 
def parse_input(data: Union[str, int]) -> str:
    return str(data)  # 接受 str 或 int,返回 str
 
# Python 3.10+ 等价写法
def parse_input_v2(data: str | int) -> str:
    return str(data)
 
print(parse_input(100))    # 输出 "100"
print(parse_input("abc")) # 输出 "abc"
 
 

Optional:可选类型‌

等价于 Union[T, None],表示变量可能是某个类型或 None。

 
from typing import Optional
 
def find_user(user_id: int) -> Optional[str]:
    if user_id == 1:
        return "Alice"  # 返回 str
    else:
        return None     # 返回 None
 
user = find_user(1)
if user is not None:
    print(user.upper())  # 类型检查器知道此处 user 是 str
 
 

容器类型

List/list:列表‌

指定列表元素的类型。

 
from typing import List
 
def sum_numbers(nums: List[int]) -> int:
    return sum(nums)  # 确保列表元素全是 int
 
# Python 3.9+ 内置泛型写法
def sum_numbers_v2(nums: list[int]) -> int:
    return sum(nums)
 
sum_numbers([1, 2, 3])  # 合法
sum_numbers(["a", "b"]) # 类型检查报错(元素不是 int)
 
 

Dict / dict:字典‌

指定键和值的类型。

 
from typing import Dict
 
def get_population() -> Dict[str, int]:
    return {"China": 1410, "India": 1380}  # 键为 str,值为 int
 
# Python 3.9+ 写法
def get_population_v2() -> dict[str, int]:
    return {"China": 1410, "India": 1380}
 
population = get_population()
print(population["China"] + 10)  # 类型检查器知道值是 int
 
 

Tuple / tuple:元组‌

  • ‌固定长度和类型‌:精确指定每个位置的类型。
  • ‌不定长元组‌:用 ... 表示剩余元素类型相同。
 
from typing import Tuple
 
def get_coordinates() -> Tuple[float, float]:
    return (40.7128, -74.0060)  # 两个 float 元素
 
# 不定长元组(Python 3.9+)
def process_data(data: tuple[int, ...]) -> int:
    return sum(data)  # 所有元素必须是 int
 
process_data((1, 2, 3))    # 合法
process_data((1, "a"))     # 类型检查报错(第二个元素不是 int)
 
 

Set / set:集合‌

指定集合元素的类型。

 
from typing import Set
 
def unique_values(items: Set[str]) -> int:
    return len(items)  # 元素必须是 str
 
# Python 3.9+ 写法
def unique_values_v2(items: set[str]) -> int:
    return len(items)
 
unique_values({"apple", "banana"})  # 合法
unique_values({1, 2})              # 类型检查报错
 
 

泛型与类型变量

TypeVar:定义泛型类型变量‌

用于定义“类型变量”,让函数、类、类型别名在静态检查阶段具备参数化的能力(类似 C++/Java 的模板或泛型)。

 
from typing import TypeVar, Generic, List
 
T = TypeVar('T')  # 可以是任何类型
 
def first_element(items: List[T]) -> T:
    return items  # 返回类型与列表元素类型相同
 
print(first_element([1, 2, 3]))    # 返回 int 类型(1)
print(first_element(["a", "b"]))   # 返回 str 类型("a")
 
 
 
T = TypeVar('T')          # 可以是任何类型
S = TypeVar('S', bound=str)   # 必须是 str 的子类型
U = TypeVar('U', int, float)  # 只能是 int 或 float(约束)
 
T_co = TypeVar('T_co', covariant=True)      # 协变:允许子类型替换
T_contra = TypeVar('T_contra', contravariant=True)  # 逆变:允许父类型替换
 
 

Sequence

只读可变长度序列”的泛型协议,用于描述“像列表一样的东西”,但不需要是 list 本身。

类型可否修改是否可变长度示例
Sequence[T]❌ 只读tuple[int, ...]List[int]
MutableSequence[T]✅ 读写List[int]deque[int]
Iterable[T]只读迭代器range(10)generator
Container[T]只读 in 检查set[int]

作为函数参数

from typing import Sequence
 
def total(nums: Sequence[int]) -> int:
    return sum(nums)
 
total([1, 2, 3])        # ✅ list
total((1, 2, 3))        # ✅ tuple
total(range(5))         # ✅ range
total("123")            # ✅ str 也是 Sequence[str]

作为返回值

from typing import Sequence
 
def squares(n: int) -> Sequence[int]:
    return [i * i for i in range(n)]  # 返回 list,但符合 Sequence[int]

协变(covariant)

from typing import Sequence, TypeVar
 
T_co = TypeVar('T_co', covariant=True)
 
def animals(x: Sequence[T_co]) -> T_co:
    return x[0]
 
cats: Sequence[Cat] = [Cat()]
animals(cats)  # ✅ 协变允许

联合类型

from typing import Sequence, Union
 
NumberSeq = Sequence[Union[int, float]]
 
def avg(nums: NumberSeq) -> float:
    return sum(nums) / len(nums)

TypeVarTuple

Python 3.11 起引入的 TypeVarTuple(PEP 646)提供了 可变长泛型(variadic generics) 能力,

允许一个泛型类型或函数接受 “任意数量”的类型参数,从而精确描述诸如“任意维度的张量”、“任意长度的元组”等场景。

基本语法

from typing import TypeVarTuple
 
Ts = TypeVarTuple('Ts')      # 一个类型变量元组
 
# ✅ 正确:始终用 * 展开
def foo(*args: *Ts) -> tuple[*Ts]: ...
 
# ❌ 错误:忘记 *
def bar(x: tuple[Ts]) -> ...     # Type checker 报错

函数:任意长度元组变换

from typing import TypeVar, TypeVarTuple
 
T  = TypeVar('T')
Ts = TypeVarTuple('Ts')
 
def move_first(t: tuple[T, *Ts]) -> tuple[*Ts, T]:
    return (*t[1:], t[0])
 
reveal_type(move_first((1, 'a', True)))   # tuple[str, bool, int]
 
 

泛型类:任意维度数组

from typing import Generic, TypeVar, TypeVarTuple
 
DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')
 
class Array(Generic[DType, *Shape]):
    def __abs__(self) -> Array[DType, *Shape]: ...
    def broadcast_to(self, new_shape: tuple[*Shape]) -> Array[DType, *Shape]: ...
 
# 使用
Height = int
Width  = int
img: Array[float, Height, Width, 3] = Array()
 

默认值

from typing import Unpack, TypeVarTuple
 
DefaultShape = TypeVarTuple('DefaultShape', default=Unpack[tuple[int, int]])
 
class Tensor[*DefaultShape]:
    ...

Generic:泛型类‌

创建可参数化的类。

 
from typing import Generic, TypeVar, List
 
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()
 
# 实例化时指定具体类型
stack_int = Stack‌:ml-search[int]
stack_int.push(42)
stack_int.push("hello")  # 类型检查报错(非 int 类型)
 
stack_str = Stack‌:ml-search[str]
stack_str.push("world")  # 合法
 
 

函数与回调

Callable:函数签名‌

定义回调函数的参数和返回值类型。

Callable[..., Any]

方括号里是 参数列表 和 返回值 的“类型提示”。

  • ...(三个点,Ellipsis)是“参数形状不关心”的简写:
    • 不限制参数个数、不限制参数类型。
    • 等价于写 typing.ParamSpec 里的 *args, **kwargs 的“通配”效果。
  • 最后一个 Any 表示“返回值类型也不限制”。
 
from typing import Callable
 
def apply_operation(
    x: int, 
    y: int, 
    op: Callable[[int, int], int]  # 接受两个 int 参数,返回 int
) -> int:
    return op(x, y)
 
# 合法操作
print(apply_operation(3, 4, lambda a, b: a * b))  # 输出 12
 
# 非法操作(类型检查报错)
apply_operation(3, 4, lambda a: a)  # 回调函数参数数量不匹配
 
 

迭代器和生成器

  • Generator[YieldType, SendType, ReturnType]‌:定义生成器的产出值、发送值和返回值。
  • Iterable[T]‌:可迭代对象。
  • Iterator[T]‌:迭代器。
 
from typing import Generator, Iterable, Iterator
 
def count_up_to(n: int) -> Generator[int, None, None]:
    """生成器产出 int,不接收 send 值,无返回值"""
    i = 1
    while i <= n:
        yield i
        i += 1
 
def print_all(items: Iterable[str]) -> None:
    for item in items:
        print(item)
 
def square_numbers(nums: list[int]) -> Iterator[int]:
    return (num ** 2 for num in nums)  # 返回生成器表达式(迭代器)
 
# 使用示例
for num in count_up_to(5):
    print(num)  # 输出 1, 2, 3, 4, 5
 
 

高级类型

Literal

字面量类型(Python 3.8+)‌,限制变量只能取特定的字面量值。

from typing import Literal
 
def set_status(status: Literal["active", "inactive"]) -> None:
    print(f"Status set to {status}")
 
set_status("active")   # 合法
set_status("pending")  # 类型检查报错(不在允许的字面量中)
 
 

TypedDict

类型化字典(Python 3.8+)‌,定义字典的键和值类型,增强代码可读性和安全性。

 
from typing import TypedDict
 
class User(TypedDict):
    name: str
    age: int
    is_active: bool
 
def create_user() -> User:
    return {
        "name": "Bob",
        "age": 30,
        "is_active": True,
        "email": "bob@example.com"  # 类型检查报错(不在定义中)
    }
 
 

Protocol

结构子类型(Python 3.8+)‌,定义接口协议(无需继承,只要实现特定方法)。

 
from typing import Protocol
 
class Printable(Protocol):
    def print(self) -> None:
        ...  # 只需实现 print 方法即可符合协议
 
class Document:
    def print(self) -> None:
        print("Printing document...")
 
class Image:
    def print(self) -> None:
        print("Printing image...")
 
def log_print(obj: Printable) -> None:
    obj.print()
 
log_print(Document())  # 合法
log_print(Image())     # 合法
 
 

其他实用类型

NewType:创建新类型‌

定义具有语义的新类型(运行时无开销,仅用于类型检查)。

 
from typing import NewType
 
UserId = NewType("UserId", int)  # UserId 是 int 的派生类型
 
def get_user(id: UserId) -> str:
    return f"User {id}"
 
user_id = UserId(1001)  # 创建 UserId 类型变量
print(get_user(user_id))  # 合法
 
# 直接传递 int 会报错
print(get_user(1001))  # 类型检查报错(需要 UserId 类型)
 
 

Type:类类型‌

表示类本身(而非实例),常用于工厂模式。

  • Type[Dog] 只接受 Dog 及其子类
  • Type[object] 接受 任何类
 
from typing import Type
 
class Animal:
    def speak(self) -> str:
        return "..."
 
class Dog(Animal):
    def speak(self) -> str:
        return "Woof!"
 
def create_animal(cls: Type[Animal]) -> Animal:
    return cls()  # 创建类的实例
 
print(create_animal(Dog).speak())  # 输出 "Woof!"
 
 

Final

常量(Python 3.8+)‌,标记变量不可被重新赋值。

from typing import Final
 
MAX_SIZE: Final[int] = 100
MAX_SIZE = 200  # 类型检查报错(不可修改)
 
 

Annotated

Annotated 允许你在类型提示中添加元数据,这可以用于验证或文档目的。

给类型附加任意元数据的机制,而不改变运行时的实际类型。

注意:大量使用可能影响启动时间(但运行时无影响)

from typing import Annotated
 
def process_value(value: Annotated[int, "Must be a positive integer"]) -> int:
    if value < 0:
        raise ValueError("Value must be positive!")
    return value
 
print(process_value(10))  # 正确
# print(process_value(-1))  # 会抛出 ValueError
 

基本用法

from typing import Annotated
 
# 基本语法
# Annotated[<type>, <metadata1>, <metadata2>, ...]
 
# 1) 定义
UserId = Annotated[int, "用户ID", range(1, 9999)]
 
# 2) 使用
def get_user(uid: UserId) -> dict:
    ...
 
  • 运行时:uid 仍然是普通的 int,没有任何额外行为。
  • 静态工具 / 运行时库:可以读取 "用户ID" 或 range(...) 做验证、文档或 IDE 提示。

工作原理

Annotated[T, *metadata] 返回一个特殊的“代理类型”,元数据存放在 metadata 属性里:

>>> from typing import get_type_hints, get_origin, get_args
>>> get_type_hints(get_user)          # 返回裸 int,不影响
{'uid': <class 'int'>}
>>> get_origin(UserId), get_args(UserId)
(<class 'int'>, ('用户ID', range(1, 9999)))
 

常见场景实战

场景示例使用者
数值约束Annotated[int, Field(ge=0, le=120)]Pydantic
字符串格式Annotated[str, StringConstraints(min_length=3)]Pydantic
依赖注入Annotated[Database, Depends(get_db)]FastAPI
命令行选项Annotated[int, typer.Argument(help="端口")]Typer
数据库Annotated[str, Column(String(50))]SQLAlchemy 2.0

FastAPI 参数扩展

from fastapi import FastAPI, Query
from typing import Annotated
 
app = FastAPI()
 
@app.get("/items/")
async def read_items(
    q: Annotated[str, Query(min_length=3, max_length=50)]
):
    return {"q": q}

添加验证约束

from pydantic import BaseModel, Field
from typing import Annotated
 
class User(BaseModel):
    age: Annotated[int, Field(gt=0, lt=120)]
    email: Annotated[str, Field(pattern=r".+@.+\..+")]

业务语义标注

from typing import Annotated
 
# 标记敏感数据
Password = Annotated[str, "sensitive_data"]
 
# 标记测量单位
Temperature = Annotated[float, "unit:celsius"]

运行时访问元数据

from typing import Annotated, get_type_hints
 
def func(param: Annotated[int, "range:1-100"]):
    pass
 
hints = get_type_hints(func, include_extras=True)
print(hints["param"].__metadata__)  # 输出: ("range:1-100",)
 

嵌套与组合

PositiveInt = Annotated[int, Range(1, 999)]
UserId      = Annotated[PositiveInt, "Primary key"]
 
def create(uid: UserId) -> None: ...

Self

Self 类型简化了返回当前实例的方法的类型注解。

from typing import Self
 
class Builder:
    def __init__(self):
        self.data = []
 
    def add(self, value: int) -> Self:
        self.data.append(value)
        return self
 
builder = Builder().add(10).add(20)
print(builder.data)  # 输出: [10, 20]
 

Unpack 和变长泛型

Unpack 用于变长泛型,允许你解包类型序列。

 
from typing import TypeVarTuple, Unpack
 
Shape = TypeVarTuple("Shape")
 
def print_shapes(*shapes: Unpack[Shape]) -> None:
    for shape in shapes:
        print(shape)
 
print_shapes(1, 2, 3)  # 正确
 

协变与逆变

协变(Covariant)‌

允许子类替代父类(常见于只读容器)。

 
from typing import TypeVar, Generic
 
class Animal: pass
class Dog(Animal): pass
 
T_co = TypeVar("T_co", covariant=True)
 
class ImmutableList(Generic[T_co]):
    def __init__(self, items: list[T_co]) -> None:
        self.items = items
    
    def get(self, index: int) -> T_co:
        return self.items[index]
 
# 协变允许将 ImmutableList[Dog] 赋值给 ImmutableList[Animal]
animals: ImmutableList[Animal] = ImmutableList([Dog(), Dog()])
 
 

逆变(Contravariant)‌

允许父类替代子类(常见于函数参数)。

 
from typing import TypeVar, Generic, Callable
 
class Animal: pass
class Dog(Animal): pass
 
T_contra = TypeVar("T_contra", contravariant=True)
 
class EventHandler(Generic[T_contra]):
    def handle(self, data: T_contra) -> None:
        print(f"Handling {type(data).__name__}")
 
# 逆变允许将 EventHandler[Animal] 赋值给 EventHandler[Dog]
def process_event(handler: EventHandler[Dog]) -> None:
    handler.handle(Dog())
 
animal_handler = EventHandler‌:ml-search[Animal]
process_event(animal_handler)  # 合法