资源的层级关系
示例代码:
bash
# 用户和文章的关系设计
class User(BaseModel):
id: int
name: str
email: str
class Post(BaseModel):
id: int
title: str
content: str
author_id: int
# 主资源操作
@app.get("/users")
async def list_users() -> List[User]:
return users
@app.get("/posts")
async def list_posts() -> List[Post]:
return posts
# 关联资源操作
@app.get("/users/{user_id}/posts")
async def get_user_posts(user_id: int) -> List[Post]:
"""获取特定用户的所有文章"""
return [post for post in posts if post.author_id == user_id]
@app.get("/posts/{post_id}/author")
async def get_post_author(post_id: int) -> User:
"""获取文章的作者信息"""
post = find_post(post_id)
if not post:
raise HTTPException(status_code=404, detail="Post not found")
return find_user(post.author_id)
先一句话讲透:这是 RESTful 风格的「资源嵌套路由」,用来体现「用户 ↔ 文章」一对多的层级从属关系,下面逐层拆解,结合代码、关系、路由规则讲明白。
一、先理清业务实体关系
User(用户) 和 Post(文章)是 一对多:
-
一个用户可以发布多篇文章
-
一篇文章只属于一个作者(用户)
-
靠
Post.author_id外键关联:文章里存作者ID,实现两者绑定User(1) ──┬── Post(1)
├── Post(2)
└── Post(3)
二、两类路由的区别:平级路由 vs 嵌套路由
代码里分了两种接口,对应 REST 两种资源访问形式:
1. 平级路由(独立资源)
python
# 查所有用户
@app.get("/users")
# 查所有文章
@app.get("/posts")
含义:把用户、文章当成两个独立顶级资源 ,直接全量查询,不体现从属关系。
访问示例:
GET /users→ 全部用户GET /posts→ 全部文章
2. 嵌套路由(体现层级/从属关系,重点)
REST 规范里,从属资源会把父资源ID写在URL路径里,用来表达「谁的东西」。
① /users/{user_id}/posts → 用户 → 旗下文章
路由语义:获取「指定用户」所拥有的全部文章
{user_id}:先定位父资源(某个用户)- 后面
/posts:再取该用户下的子资源(文章)
执行逻辑:
- 路径拿到
user_id - 遍历所有文章,筛选出
author_id == user_id的记录返回
访问示例:
GET /users/1/posts → 查询 ID=1 这个用户发的所有文章
② /posts/{post_id}/author → 文章 → 所属作者
路由语义:获取「指定文章」对应的作者
{post_id}:先定位父资源(某篇文章)- 后面
/author:取该文章关联的子/关联资源(作者)
执行逻辑:
- 根据
post_id找到对应文章 - 取出文章的
author_id - 再根据作者ID查到对应用户并返回
访问示例:
GET /posts/5/author → 查询 ID=5 这篇文章的作者信息
三、为什么要这么设计?核心目的
-
语义直观,URL 自带业务关系
看 URL 就知道层级:
/用户ID/文章,一眼明白是「该用户的文章」,符合 REST 设计思想。 -
天然做数据过滤
不用在查询参数里传
?user_id=xxx,而是把归属关系融入路径,语义更标准。对比:
- 不嵌套:
/posts?user_id=1(参数传过滤条件) - 嵌套:
/users/1/posts(路径表达归属)
- 不嵌套:
-
统一资源访问范式
后续新增操作也可以沿用这套层级,比如:
- 给指定用户新增文章:
POST /users/{user_id}/posts - 删除某用户下某篇文章:
DELETE /users/{user_id}/posts/{post_id}
- 给指定用户新增文章:
四、逐行解读关联接口代码
接口1:获取某个用户的所有文章
python
@app.get("/users/{user_id}/posts")
async def get_user_posts(user_id: int) -> List[Post]:
# 遍历全部文章,只返回作者ID等于当前用户ID的文章
return [post for post in posts if post.author_id == user_id]
逻辑极简:靠外键 author_id 匹配,实现父子资源关联查询。
接口2:获取某篇文章的作者
python
@app.get("/posts/{post_id}/author")
async def get_post_author(post_id: int) -> User:
# 1. 先根据文章ID找到文章
post = find_post(post_id)
if not post:
raise HTTPException(status_code=404, detail="Post not found")
# 2. 拿文章里的作者ID,反查用户
return find_user(post.author_id)
反向关联:从子资源(文章)通过外键,回查父资源(用户)。
五、补充:两种设计风格对比(面试/实战常用)
| 方式 | 路由示例 | 特点 | 适用场景 |
|---|---|---|---|
| 平级路由 + 查询参数 | /posts?user_id=1 |
简单灵活,URL短 | 简单查询、多条件筛选 |
| 嵌套层级路由 | /users/1/posts |
体现资源从属关系,REST 标准 | 一对多固定从属关系(用户-文章、分类-商品等) |
你这段代码,就是典型 RESTful 嵌套资源路由,用 URL 结构 + 模型外键,双重表达「用户和文章的一对多层级关系」。
六、快速总结
- 实体关系:User 一对多 Post ,靠
author_id字段关联; - 路由设计:
/users//posts:独立顶级资源,查全量;/users/{id}/posts:父→子,查某个用户的所有文章;/posts/{id}/author:子→父,查某篇文章的作者;
- 核心思想:用 URL 路径体现资源层级,用数据表字段实现数据关联。
安全性-幂等性
一、两个核心定义
1. 安全性(Safe)
是否修改服务端资源状态
- ✅ 安全 :只读操作,不会新增/修改/删除数据,服务端状态不变
- ❌ 不安全:会改动服务端数据(增、改、删)
注意:安全 ≠ 不会报错,只是不改变业务数据。
2. 幂等性(Idempotent)
多次执行同一个请求,效果和返回结果 是否和执行一次完全一致
- ✅ 幂等:请求发 1 次、N 次,最终数据、响应状态都一样
- ❌ 非幂等:多发几次,数据/结果会变(重复请求会产生副作用)
二、逐 HTTP 方法拆解(结合你代码)
1. GET /users/{user_id}
标签:安全且幂等
- 安全:只查询用户,不改动任何数据
- 幂等:不管刷多少次接口,查到的数据不变,服务端无变化
- 场景:纯查询类接口统一遵循该特性。
2. POST /users(创建用户)
标签:不安全且非幂等
- 不安全:新增用户,修改了服务端数据
- 非幂等:连续发两次相同请求 → 创建两条一模一样的用户,数据被重复新增,结果不同
- 补充:所以创建接口一般用 POST,前端要做防重复提交。
3. PUT /users/{user_id}(全量替换资源)
标签:不安全但幂等
- 不安全:覆盖/新建用户,改动数据
- 幂等:
- 用户已存在:多次全量更新,最终数据完全一致;
- 用户不存在:按指定 ID 创建,多次请求也只会生成这一条数据;
- 特点:PUT 语义是用请求体完整替换目标资源,天生设计为幂等。
4. PATCH /users/{user_id}(部分更新)
标签:不安全且通常非幂等
- 不安全:修改部分字段,改动数据
- 大多非幂等:
例:接口逻辑是积分 +1、阅读数 +1,多次请求数值会不断累加,结果不一样; - 特例:如果只是
把昵称统一改成"test",这种简单赋值也能做到幂等,但业务上极少,所以约定为「通常非幂等」。
5. DELETE /users/{user_id}(删除用户)
标签:不安全但幂等
- 不安全:删除数据,改变服务端状态
- 幂等:
- 第一次请求:用户被删除,返回 204;
- 后续再删同个 ID:用户已不存在,依旧返回 204;
多次请求最终状态一致,符合幂等。
你代码里也体现了这一点:不存在也正常返回成功。
三、汇总对照表(方便记忆)
| 请求方法 | 安全性 | 幂等性 | 核心语义 |
|---|---|---|---|
| GET | 安全 | 幂等 | 查询资源 |
| POST | 不安全 | 非幂等 | 新建资源 |
| PUT | 不安全 | 幂等 | 全量替换资源 |
| PATCH | 不安全 | 通常非幂等 | 局部更新资源 |
| DELETE | 不安全 | 幂等 | 删除资源 |
四、补充实战要点(面试/开发常用)
-
为什么要关心这两个特性?
用于接口设计、重试策略、网关限流、防重、分布式请求处理:
- GET/ PUT/ DELETE 可放心做自动重试;
- POST、累加类 PATCH 不能随意重试,必须前端/业务层做防重。
-
易错点区分
- 不要把「安全」理解成「防攻击」,REST 里的安全特指是否改数据;
- PUT 可以创建资源(按指定 ID),这是规范允许的,和 POST 创建(服务端分配ID)是两种设计。
-
结合你代码的细节
DELETE 里判断用户不存在仍返回 204,就是刻意保证幂等的标准写法。
Field------Pydantic 字段增强核心工具
前置说明:
导入:
from pydantic import Field作用:对模型字段做元数据配置、默认值、数据校验、别名、文档、序列化控制,是 Pydantic 字段增强核心工具。
一、基础语法
python
字段名: 类型 = Field(
# 位置/关键字参数
default=..., # 静态默认值
default_factory=...,# 动态工厂函数
title=...,
description=...,
example=...,
alias=...,
validation规则...,
exclude=...,
include=...
)
两个特殊占位符
Field(...)/...:表示必填字段(无默认值,必须传参)None:显式默认值为None(可选字段)
二、核心参数分类 + 逐参数详解 + 示例
按功能分为 6 大类:必填/默认值、数据校验、别名、文档注释、序列化控制、扩展配置。
1. 默认值相关(最常用)
1.1 default:静态固定默认值
适用于字符串、数字、布尔等不可变类型,实例化时直接使用固定值。
python
from pydantic import BaseModel, Field
class User(BaseModel):
# 静态默认:性别默认男
gender: str = Field(default="male")
# 等价简写(简单固定值可直接写)
age: int = 18
1.2 default_factory:动态生成默认值
重点 :接收无参函数 ,每次实例化模型时执行一次 ,生成全新值。
适用场景:
- 时间
datetime.now - 可变容器
list / dict / set(解决Python默认值共享坑) - 自定义对象、随机值等动态数据
示例1:时间戳(对应你之前的响应体)
python
from datetime import datetime
class APIResp(BaseModel):
# 每次创建实例,都获取当前时间
timestamp: datetime = Field(default_factory=datetime.now)
示例2:可变容器 list/dict(经典避坑)
python
# ❌ 错误写法:所有实例共享同一个列表,数据串扰
# tags: list = []
# ✅ 正确写法
class Article(BaseModel):
tags: list[str] = Field(default_factory=list)
extra: dict = Field(default_factory=dict)
a1 = Article()
a1.tags.append("技术")
a2 = Article()
print(a2.tags) # [] 互不干扰
示例3:自定义工厂函数
python
def get_random_code() -> str:
import random
return f"CODE_{random.randint(1000,9999)}"
class Order(BaseModel):
code: str = Field(default_factory=get_random_code)
1.3 字段设为【必填】
使用 ... 表示该字段必须传值,无默认:
python
class User(BaseModel):
# 必填,不允许为空
username: str = Field(...)
# 等价简写(推荐简单场景)
password: str
2. 数据校验参数(约束字段格式/范围)
Pydantic 内置大量校验规则,Field 直接挂载使用,自动抛出校验异常,FastAPI 会自动捕获并返回 422。
2.1 字符串校验
min_length:最小长度max_length:最大长度pattern:正则表达式
python
class User(BaseModel):
# 用户名:3~20位
username: str = Field(min_length=3, max_length=20, description="账号名称")
# 手机号正则校验
phone: str = Field(pattern=r"^1[3-9]\d{9}$")
2.2 数字校验(int/float)
gt:大于(greater than)ge:大于等于(greater or equal)lt:小于(less than)le:小于等于(less or equal)multiple_of:是某个数的倍数
python
class Goods(BaseModel):
# 价格 > 0
price: float = Field(gt=0)
# 数量 1 ~ 100
count: int = Field(ge=1, le=100)
2.3 组合校验示例(FastAPI 表单常用)
python
class RegisterForm(BaseModel):
email: str = Field(max_length=50)
pwd: str = Field(min_length=6, max_length=32, description="登录密码")
3. 别名相关:alias / alias_priority
3.1 alias 字段别名
作用:前端传参名 和 后端模型字段名不一致时做映射。
- 接收请求时:优先使用
alias名称解析 - 响应返回时:默认返回原字段名(可配合序列化修改)
python
class User(BaseModel):
# 前端传 user_name,后端模型用 username
username: str = Field(alias="user_name")
# 前端传 {"user_name": "张三"}
u = User(user_name="张三")
print(u.username) # 张三
3.2 alias_priority 别名优先级
控制「别名」和「原字段名」的解析优先级,一般配合模型继承、多数据源使用。
4. 文档相关参数(FastAPI 自动生成接口文档)
FastAPI 的 Swagger / ReDoc 文档,完全读取 Field 中的文档配置。
title:字段简短标题description:字段详细描述example:示例值(文档展示、调试默认填充)examples:多组示例(数组)
python
class LoginForm(BaseModel):
account: str = Field(
title="登录账号",
description="支持手机号/邮箱登录",
example="13800138000"
)
pwd: str = Field(
description="6-32位密码",
examples=["123456", "Abc@123456"]
)
效果:打开 /docs 接口文档,会自动展示描述和示例。
5. 序列化控制:exclude / include
控制模型转字典/JSON 时,是否忽略该字段 。
常用于:隐藏密码、内部字段、临时字段。
5.1 exclude=True 序列化时排除字段
python
class User(BaseModel):
id: int
name: str
# 序列化时隐藏密码,不返回前端
password: str = Field(exclude=True)
u = User(id=1, name="李四", password="123456")
print(u.model_dump())
# {'id': 1, 'name': '李四'} 无 password
5.2 include=True 强制包含(默认都包含,极少用)
6. 其他常用实用参数
6.1 repr=False 不在打印日志中显示
敏感字段(密码、密钥)禁止在 print()、日志中输出:
python
class User(BaseModel):
name: str
# 打印对象时隐藏该字段
secret_key: str = Field(repr=False)
u = User(name="王五", secret_key="abc123")
print(u)
# name='王五' 看不到 secret_key
6.2 frozen=True 字段只读(不可修改)
设置后字段实例化完成后禁止重新赋值:
python
class Config(BaseModel):
app_id: str = Field(frozen=True)
c = Config(app_id="APP001")
# c.app_id = "APP002" # 报错:字段已冻结,无法修改
三、Field 三种典型写法对比(必分清)
python
from pydantic import BaseModel, Field
from datetime import datetime
class Demo(BaseModel):
# 1. 简单固定默认值 → 直接赋值(推荐)
name: str = "默认名称"
# 2. 复杂配置 + 固定默认 → Field + default
age: int = Field(default=18, ge=0, description="年龄")
# 3. 动态值/可变容器 → Field + default_factory
create_time: datetime = Field(default_factory=datetime.now)
tags: list[str] = Field(default_factory=list)
# 4. 必填字段(无默认)
email: str = Field(..., description="邮箱,必填")
四、高频踩坑点(面试/实战易错)
坑1:可变类型直接写默认值
python
# ❌ 错误:list/dict 不能直接当默认值
data: list = []
# ✅ 正确:必须用 default_factory
data: list = Field(default_factory=list)
原因:Python 函数/类默认值只会在定义时创建一次,所有实例共享同一个对象。
坑2:default 和 default_factory 不能同时使用
二选一,同时写会报错。
坑3:default_factory 函数不能传参
如果需要传参,用嵌套函数/闭包包装:
python
# 需求:默认拼接前缀
def make_prefix(prefix: str):
def inner():
return f"{prefix}_001"
return inner
class Demo(BaseModel):
code: str = Field(default_factory=make_prefix("ORDER"))
坑4:区分「安全默认值」
- 不可变类型(
int/str/bool)→ 直接赋值 - 动态值/可变容器(
datetime/list/dict)→default_factory
五、FastAPI 综合实战完整示例
结合标准化响应体 + 校验 + 文档 + 动态时间,复刻你之前的代码并强化:
python
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, Field
class APIResponse(BaseModel):
# 静态默认值
success: bool = Field(default=True, description="请求是否成功")
message: str = Field(default="操作成功", description="提示信息")
# 可选字段,默认None
data: Optional[dict] = Field(None, description="业务数据")
errors: Optional[List[str]] = Field(None, description="错误详情列表")
# 动态生成当前时间戳
timestamp: datetime = Field(
default_factory=datetime.now,
description="接口响应时间"
)
六、面试精简总结(背诵版)
Field是 Pydantic 用来增强模型字段的工具,扩展默认值、校验、别名、文档、序列化能力。default:设置静态固定默认值,用于普通类型。default_factory:接收无参函数,每次实例化动态生成值 ,专门解决datetime、list/dict等可变/动态类型的共享问题。- 内置
gt/lt/min_length/pattern等参数做自动数据校验,FastAPI 自动返回 422。 alias实现前后端字段名映射;description/example驱动接口文档;exclude控制序列化字段显隐。- 核心原则:静态值用 default,动态/可变容器必须用 default_factory。
工厂函数
一、最简定义
工厂函数 :
本身就是普通函数 ,作用是 专门用来「创建并返回对象/值」 。
它不做复杂业务逻辑,只负责生产数据、实例,像一个"加工厂"。
关键词:调用它 → 得到一个新值/新对象。
二、先看普通函数 vs 工厂函数
1. 普通函数(做计算、逻辑)
python
def add(a, b):
return a + b # 做加法运算,不是工厂
2. 工厂函数(只负责产出新数据)
无参、调用就返回新内容,就是最典型的工厂函数:
python
# 工厂函数1:返回当前时间
from datetime import datetime
def get_now():
return datetime.now()
# 工厂函数2:返回空列表
def empty_list():
return []
# 工厂函数3:生成随机编号
import random
def gen_code():
return f"ORD_{random.randint(1000,9999)}"
调用一次,产出一个全新结果,这就是工厂函数的核心。
三、结合你之前的代码:default_factory 为什么要传工厂函数?
回顾代码:
python
from pydantic import BaseModel, Field
from datetime import datetime
class APIResponse(BaseModel):
# default_factory 要求传入一个【工厂函数】
timestamp: datetime = Field(default_factory=datetime.now)
1. 拆解 datetime.now
datetime.now:函数本身(工厂函数)datetime.now():函数调用,得到时间对象
2. 两种写法本质区别(重点)
写法1(错误:直接传调用结果)
python
# ❌ 模块加载时,只执行 1 次,时间永久固定
timestamp: datetime = datetime.now()
程序一启动,立刻执行 datetime.now(),生成唯一固定时间,后面所有实例共用。
写法2(正确:传工厂函数本身)
python
# ✅ 每次创建模型实例时,Pydantic 自动调用这个工厂函数
timestamp: datetime = Field(default_factory=datetime.now)
流程:
- 你创建
APIResponse()实例 - Pydantic 内部自动执行
datetime.now() - 拿到当前最新时间 赋值给
timestamp
👉 default_factory 接收的就是无参工厂函数,框架帮你在合适时机调用它生产值。
四、为什么列表/字典也必须用工厂函数?
Python 经典坑:可变对象默认值全局共享
Python 类属性的默认值,是在「定义类的时候」就一次性创建好,而非实例化对象时创建
因此需要注意:类属性默认值,不可变类型直接写,可变容器 (list/dict/set) 绝对不要直接赋值,优先用构造函数 / default_factory。
错误示范
python
class Demo(BaseModel):
# 加载类时创建唯一空列表,所有实例共用
tags: list[str] = [] # tags在定义Demo类时就会被创建,而非实例化Demo时才创建
d1 = Demo()
d1.tags.append("python")
d2 = Demo()
print(d2.tags) # ['python'] 数据串了!
用工厂函数修复
python
class Demo(BaseModel):
# 每次实例化 → 调用 list() → 生成全新空列表
tags: list[str] = Field(default_factory=list)
d1 = Demo()
d1.tags.append("python")
d2 = Demo()
print(d2.tags) # [] 正常隔离
这里 list 本身也是 Python 内置工厂函数 ,调用 list() 就生成新列表。
五、自定义工厂函数(实战扩展)
如果默认逻辑不够,自己写工厂函数:
python
from pydantic import BaseModel, Field
import random
# 自定义工厂函数:生成随机订单号
def create_order_no():
return f"ORDER_{random.randint(10000, 99999)}"
class Order(BaseModel):
# 每次新建订单,自动调用工厂函数生成单号
order_no: str = Field(default_factory=create_order_no)
status: int = 0
o1 = Order()
o2 = Order()
print(o1.order_no)
print(o2.order_no) # 两个编号完全不同
六、补充:两个容易混淆的概念(面试区分)
1. 本节讲的:简单工厂函数(Pydantic 场景)
- 就是普通函数
- 职责:生成一个值/内置对象(时间、列表、字符串)
- 用途:给字段动态默认值
2. 设计模式:工厂模式 / 工厂方法(面向对象)
属于代码架构设计,用来创建复杂类实例,和上面不是一回事,只是名字都带"工厂"。
日常写 FastAPI + Pydantic 时,提到「工厂函数」,基本都是指
default_factory这种值生成函数。
七、一句话总结(背诵版)
- 工厂函数 = 专门用来生成新值/新对象的普通函数;
default_factory要求传入无参工厂函数 ,框架会在每次实例化模型时自动调用;- 作用:解决动态时间、列表、字典这类可变/动态数据的默认值共享问题;
- 写法区别:传函数名
datetime.now,不要传调用datetime.now()。
详细解释------为什么列表/字典也必须用工厂函数?
核心原因:Python 类属性的默认值,是在「定义类的时候」就一次性创建好,而非实例化对象时创建 ,加上列表是可变对象,所有实例会共用这同一个列表。
一、先梳理完整执行顺序
第1步:解释器加载代码、定义 Demo 类
当 Python 读到 class Demo(BaseModel): 这一整块代码时,类就完成定义:
python
class Demo(BaseModel):
tags: list[str] = []
此时会执行:
- 创建一个空列表对象
[] - 把类属性
Demo.tags指向这个列表
👉 这个过程只执行一次,和后续创建多少个实例无关。
第2步:创建实例 d1 = Demo()
创建实例时:
- 实例
d1本身没有独立的tags属性 - 当访问
d1.tags,Python 会向上查找,找到类身上的那个全局列表 - 执行
d1.tags.append("python"):直接修改原列表内容
第3步:创建实例 d2 = Demo()
同理,d2 也没有自己的 tags,访问时同样指向类里那唯一一个列表 。
所以打印 d2.tags,自然能看到 python。
二、内存示意图(直观理解)
# 类定义阶段,生成唯一列表内存地址
Demo 类 ────→ 列表对象 [] (内存地址:0x123)
# 创建 d1、d2 实例,都不新建列表
d1 实例 ───┐
├──→ 共用 0x123 这个列表
d2 实例 ───┘
# d1.append("python") → 0x123 列表变成 ["python"]
# 所有引用这个地址的实例,看到的数据都会同步变化
三、关键区分:可变对象 vs 不可变对象
这个坑只出现在 list / dict / set 这类可变对象上,不可变类型(str/int/bool)不会有问题。
1. 不可变类型(正常,不会串数据)
python
class Demo:
num: int = 0 # 类属性,定义时创建
d1 = Demo()
d1.num = 10 # 注意:这里是「给实例新增独立属性」,不是修改类属性
d2 = Demo()
print(d2.num) # 0 正常
原因:
对 int/str 赋值 d1.num = 10,Python 会直接在实例上新建一个专属属性,不再走类属性。
2. 可变类型(列表/字典,踩坑根源)
python
class Demo:
tags: list = []
d1 = Demo()
d1.tags.append("python")
# 重点:append 是「原地修改对象内容」,没有给实例新建属性
# 所以依旧操作的是类上的共享列表
d2 = Demo()
print(d2.tags) # ['python']
总结一句话:
- 对可变对象调用方法改内容 (
append/pop/字典增删键值):修改的是共享对象,所有实例受影响; - 对变量直接赋值 (
d1.tags = [1,2]):会给实例创建独立属性,不再共享。
四、两种修复方案(对应你之前的代码)
方案1:Pydantic 专属 → Field(default_factory)(推荐)
每次实例化,都会调用工厂函数生成全新列表,每个实例独有一份:
python
from pydantic import BaseModel, Field
class Demo(BaseModel):
tags: list[str] = Field(default_factory=list)
d1 = Demo()
d1.tags.append("python")
d2 = Demo()
print(d2.tags) # [] 互不干扰
方案2:原生 Python 类写法(不用 Pydantic)
在构造函数 __init__ 里初始化列表,实例化时才创建:
python
class Demo:
def __init__(self):
# 每个实例创建时,单独生成空列表
self.tags = []
d1 = Demo()
d1.tags.append("python")
d2 = Demo()
print(d2.tags) # []
五、补充延伸(面试常问)
-
为什么 Pydantic 不推荐直接写
tags: list = []?本质就是上面的共享可变对象问题,属于 Python 语法坑,Pydantic 模型同样受这个规则约束。
-
default_factory=list做了什么?Pydantic 在每次实例化对象时 ,自动执行
list(),生成新列表并绑定到当前实例,等价于原生类在__init__里初始化。 -
避坑口诀:
类属性默认值,不可变类型直接写,可变容器(list/dict/set) 绝对不要直接赋值 ,优先用构造函数 /
default_factory。