Python 泛型:把 list[User] 讲明白

Python 泛型:把 listUser 讲明白

一篇把 Python 泛型讲透的拆解文。list[User] 到底在干嘛?TypeVar 是啥?怎么自己写一个泛型类?

前言

FastAPI 文档里"泛型"这一段,写得克制,但经常把人卡住。

不是因为它难------而是因为网上讲泛型的文章一上来就甩 TypeVarGeneric[T],没人先解释这个机制到底在解决什么问题。

读完你应该能:知道 list[User] 在干嘛、能自己写一个泛型类、能在 FastAPI 里把它用起来。


一、泛型是啥?

先看 FastAPI 文档原文:

某些类型可以在方括号内接收"类型参数",以定义其内部类型。例如,"字符串列表"可以声明为 list[str]。这些可以接收类型参数的类型被称为泛型。

一个常见反应:list[str] 就是 str 列表,这还用专门讲?

用生活例子理解:

装饼干的盒子 → Box[饼干] 装袜子的盒子 → Box[袜子]

Box[...] 就是泛型 ,方括号里那个就是类型参数

看代码:

python 复制代码
# 一开始你只知道是"列表",但里面是什么类型?
names: list       # ❌ 不知道装啥

# 加上方括号 → "字符串列表"
names: list[str]  # ✅ 明确:里面都是 str

# 字典也一样:键是 str,值是 int
scores: dict[str, int]  # {"math": 90, "english": 85}

简单说:把类型当参数,让同一份代码适配多种类型。


二、list[User] 都指定死了,还"通用"吗?

都指定成 User 了,还"通用"在哪儿?

澄清一下:泛型指的是那个能接收类型参数的模板,不是你这个具体类怎么用。

  • list 是泛型(它能接收类型参数)
  • list[User] 是把泛型实例化 了,里面的 User 是具体的类型参数

只要是 xxx[类型] 这种形式,xxx 就是泛型 ------不管你里面写的是 User 还是 T 还是 int

那"通用"到底通用在哪?

通用的是 list 这个类本身的实现------不是你这次怎么用。

如果 list 只能装一种类型会怎样?Python 的 C 实现(CPython)里需要这么写:

python 复制代码
class StringList: ...   # 装字符串的列表
class IntList: ...      # 装整数的列表
class UserList: ...     # 装 User 的列表
# ... 永远写不完

但实际上,CPython 里的 list 只有一份 实现(Objects/listobject.c):

c 复制代码
// 简化版 CPython list 的 append
int list_append(PyListObject *self, PyObject *item) {
    // item 是 PyObject* 指针,指向任何 Python 对象
    // 不管 item 实际是 str、int 还是 User,都接受
    ...
}

这份 C 代码不挑类型------你 list.append(str)list.append(int)list.append(User),跑的是同一份 C 代码

所以"通用"不在你怎么用,在 list 这个类本身。 你写 list[User] 还是 list[Order],运行时都跑同一份 CPython 源码。


三、Python 运行时泛型是装饰品

Python 运行时,方括号里的东西是装饰品。

python 复制代码
a: list[User] = [1, 2, 3]   # 运行时不会报错
b: list = [1, 2, 3]
c = [1, 2, 3]

运行时 Python 完全不挑------你往里塞啥都行。

那它有没有用?------有用,但只给外部工具用

1. 类型检查器(mypy)

用 uv 装 mypy:

bash 复制代码
uv tool install mypy

script.py

python 复制代码
class User:
    name: str
    age: int

def get_users() -> list[User]:
    return []

users = get_users()
print(users[0].name)  # OK
print(users[0].xxx)   # ❌ 错!User 没有 xxx

跑 mypy:

bash 复制代码
$ mypy script.py
script.py:10: error: "User" has no attribute "xxx"  [attr-defined]
Found 1 error in 1 file (checked 1 source file)

xxx 不存在被 mypy 抓出来了------这就是方括号的作用。

2. FastAPI / Pydantic

实际工程里,给接口配 response_model=APIResponse[User] 之后:

python 复制代码
@app.post("/lark-tools", response_model=APIResponse[User])
def create_user():
    return {"code": 0, "msg": "ok", "data": {"name": "mavis"}}  # 少一个 age

触发接口后,server 端会抛 ResponseValidationError

css 复制代码
fastapi.exceptions.ResponseValidationError: 1 validation error:
  {'type': 'missing',
   'loc': ('response', 'data', 'age'),
   'msg': 'Field required',
   'input': {'name': 'mavis'}}

Pydantic 看到 APIResponse[User],校验出 data 缺 age 字段------泛型不是装饰品,框架在用方括号做真正的运行时校验

两个层面

层面 方括号起作用吗 怎么起作用
纯 Python 解释器 方括号被忽略
类型检查器(mypy) 静态分析、代码补全
FastAPI / Pydantic 运行时校验、生成文档

所以 Python 借了泛型的语法,但运行时解释器不认它------认它的是类型检查器和框架。


四、TypeVarGeneric[T]:自己定义一个泛型类

TypeVar("T") 是啥

python 复制代码
from typing import TypeVar

T = TypeVar("T")

TypeVar("T") 创建一个叫"T"的类型变量。这里的 "T" 是个名字(类似变量名),用在错误信息里------T 这个名字在代码里就是约定的占位符,你可以改成 MyTypeK 啥的,但习惯上用单个大写字母(TKV)。

python 复制代码
T = TypeVar("MyType")  # 也行,错误信息里会显示 "MyType"

T 不是"一种类型",是"任何类型的占位符" 。你用 T 的地方,最后都会被推断成具体的某个类型。

Generic[T] 是啥

Generic[T] 让类支持类型参数。没有 Generic[T],你不能在类里用 T 这个类型变量------会直接报错。

python 复制代码
from typing import Generic, TypeVar

T = TypeVar("T")

class Box(Generic[T]):     # 声明这个类支持类型参数 T
    def __init__(self, item: T):
        self.item = item
    def get(self) -> T:    # 标返回类型也是 T
        return self.item

TypeVar 的作用:让类型"流"过去

装 mypy 后,在 script.py 里写:

python 复制代码
from typing import Generic, TypeVar

T = TypeVar("T")

class Box(Generic[T]):
    def __init__(self, item: T):
        self.item = item
    def get(self) -> T:
        return self.item

# T 推断为 str
b1 = Box("hello")
reveal_type(b1.get())  # mypy: Revealed type is "builtins.str"

# T 推断为 int
b2 = Box(42)
reveal_type(b2.get())  # mypy: Revealed type is "builtins.int"

b1 装的是 str,b1.get() 出来就是 str;b2 装的是 int,b2.get() 出来就是 int。入参是啥类型,出参就是啥类型 ------这就是 TypeVar 干的事。

如果用 Any 代替,就没这个效果:

python 复制代码
from typing import Any

class BadBox:
    def __init__(self, item: Any):
        self.item = item
    def get(self) -> Any:
        return self.item

b = BadBox("hello")
reveal_type(b.get())  # mypy: Revealed type is "Any"

Any 啥都行,但啥都不保证。


五、FastAPI 实战:通用响应包装

实战代码:

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Generic, TypeVar

app = FastAPI()

T = TypeVar("T")

class APIResponse(BaseModel, Generic[T]):
    code: int
    msg: str
    data: T

class User(BaseModel):
    name: str
    age: int

class Order(BaseModel):
    id: str
    total: float

@app.get("/user", response_model=APIResponse[User])
def get_user():
    return {"code": 0, "msg": "ok", "data": {"name": "mavis", "age": 18}}

@app.get("/order", response_model=APIResponse[Order])
def get_order():
    return {"code": 0, "msg": "ok", "data": {"id": "o001", "total": 99.0}}

跑起来:

bash 复制代码
$ uv run uvicorn main:app --reload

/user 返回的 data 按 User 校验,/order 返回的 data 按 Order 校验------一份 APIResponse[T] 代码,撑起所有返回结构。这就是泛型在工程里的价值。


总结

  • 本质:泛型 = 把类型当参数,让一份代码适配多种类型。
  • 运行时:解释器不认方括号,但 mypy / Pydantic 认。
  • 用法 :要写"装任意类型的容器"时------TypeVar 占位 + Generic[T] 让类认它。
相关推荐
2501_916007471 小时前
Python实现HTTPS爬虫的完整指南:使用requests、BeautifulSoup、Selenium和Scrapy
爬虫·python·ios·小程序·https·uni-app·iphone
gptAI_plus1 小时前
别把整个仓库塞给 AI:用 Python 生成安全的代码上下文清单
python·chatgpt
吃饱了得干活1 小时前
Agent 记忆系统:从短期记忆到长期记忆
python·langchain·agent
大鱼>1 小时前
DSPy:LLM程序自动编译与提示词优化
开发语言·人工智能·python·深度学习
2401_843253702 小时前
金融智能:AI如何重构银行业未来
人工智能·python·金融
uncle_ll2 小时前
服务器选型、微调范式、训练优化与环境搭建
服务器·python·gpt·llm·nlp
久久学姐2 小时前
Python开发爬虫的常用技术架构
爬虫·python·http·框架·数据存储
circuitsosk2 小时前
大规模离线数据管道构建:样本获取、清洗、加工与合成
人工智能·python·机器学习·搜索引擎
tang777892 小时前
分布式爬虫优化指南:如何用代理IP把采集效率提升300%
分布式·爬虫·python·tcp/ip·分布式爬虫·爬虫代理·代理ip