news 2026/10/1 2:52:15

FastAPI 从入门到实战:5 个 Demo 吃透 Python 高性能 Web 框架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastAPI 从入门到实战:5 个 Demo 吃透 Python 高性能 Web 框架

文章目录

    • 一、为什么是 FastAPI?
      • 1.1 FastAPI 是什么
      • 1.2 核心组成:Starlette + Pydantic
    • 二、环境安装与启动命令
      • 2.1 安装
      • 2.2 启动命令逐段解析
    • 三、Demo 01:第一个接口 —— Hello World
      • 3.1 完整代码
      • 3.2 逐行拆解
      • 3.3 路径参数与类型注解
    • 四、Demo 02:请求体与 Pydantic —— 对接大模型接口
      • 4.1 为什么需要 BaseModel
      • 4.2 完整代码(FastAPI + LangChain 调用 DeepSeek)
      • 4.3 关键步骤说明
      • 4.4 校验效果
    • 五、Demo 03:参数校验 —— Annotated + Path / Query
      • 5.1 Annotated 是什么
      • 5.2 Path:给路径参数加校验
      • 5.3 Query:查询参数校验
      • 5.4 三种参数工具对比
    • 六、Demo 04:请求体字段校验 —— Field 与 model_dump
      • 6.1 完整代码
      • 6.2 可选字段的读法
      • 6.3 Field:给字段加规则
      • 6.4 model_dump 与字典展开
      • 6.5 校验效果
    • 七、Demo 05:综合实战 —— Todo 待办接口
      • 7.1 完整代码(修正版)
      • 7.2 数据设计思路
      • 7.3 response_model:声明返回值结构
      • 7.4 查单条与 404
    • 八、async 异步:FastAPI 高性能的秘密
      • 8.1 通俗理解
      • 8.2 两种写法怎么选
    • 九、全文总结
    • 十、核心知识点复盘

本文基于一套循序渐进的实战 Demo(01~05),从第一个 Hello World 接口开始,经过请求体模型、参数校验、字段规则,最终完成一个 Todo 待办应用,并讲清楚 async 异步的原理。每个 Demo 的代码都完整可运行,坑点均来自真实调试过程。

一、为什么是 FastAPI?

1.1 FastAPI 是什么

FastAPI 是 Python 生态中的高性能 Web 接口框架,性能比肩 Go 和 Node.js。它的定位非常专一:只做后端 API,不用折腾繁琐配置,开发效率极高,特别适合配合 LangChain / LangGraph 开发大模型后端服务。

它打动人的三个特性:

特性说明
高性能基于异步无阻塞模型,天然适合高并发场景
省代码写少量代码即可完成接口,自带类型提示
自动文档自动生成交互式接口文档(Swagger),是前后端 API 约定的利器

1.2 核心组成:Starlette + Pydantic

【核心原理】FastAPI 本身不直接处理网络,它是站在两个库肩膀上的"组装框架":

  • Starlette:Web 底层基座。负责接收 HTTP 请求、路由匹配、返回响应、处理网络,自带异步能力。
  • Pydantic:类型校验库。功能类似前端 JS 生态里的 zod,负责校验用户输入(路径参数、查询参数、请求体),底层依赖 Python 类型注解。

请求的完整流转路径:

浏览器请求 ↓ Starlette(接收 HTTP / 路由匹配 / 异步调度) ↓ FastAPI(调度框架,串联一切) ↓ Pydantic(校验参数,不合格直接返回 422) ↓ 你写的业务函数(只关心业务逻辑) ↓ JSON 响应

一个类比帮助记忆:Starlette 是发动机,Pydantic 是安检仪,FastAPI 是把二者组装好、让你只写业务逻辑的整车厂。

二、环境安装与启动命令

2.1 安装

pipinstall"fastapi[standard]"# 安装 fastapi 全家桶(含常用依赖)pipinstall"uvicorn[standard]"# uvicorn:异步 Web 服务器,用来运行 FastAPI 项目

【重点】FastAPI 只是一个 Python 包,自己不会监听端口。必须由 uvicorn 这类 ASGI 服务器来托管运行。可以理解为:FastAPI 是应用,uvicorn 是跑应用的容器,二者缺一不可。

2.2 启动命令逐段解析

python-muvicorn main:app--reload--port8080
片段含义
python -m uvicorn用当前 Python 环境启动 uvicorn(比裸敲uvicorn更不容易用错解释器)
main:app在main.py文件里找app这个变量(冒号读作"在……里面")
--reload热重载:代码保存后自动重启服务,仅限开发环境
--port 8080监听 8080 端口

【易错点】启动日志里常见的0.0.0.0不是用来在浏览器访问的地址。host="0.0.0.0"的含义是"监听本机所有网卡",是服务端的绑定配置。浏览器测试请访问http://127.0.0.1:8000或http://localhost:8000。

三、Demo 01:第一个接口 —— Hello World

3.1 完整代码

fromfastapiimportFastAPI# 1. 创建 FastAPI 应用实例,整个服务的入口对象app=FastAPI()# 2. 装饰器:把下面的函数注册为 "/" 路径的 GET 接口@app.get("/")asyncdefroot():# 返回字典,FastAPI 自动转成 JSON 响应return{"message":"World"}# 3. 路径参数:{name} 是占位符,请求 /hello/tom 时 name = "tom"@app.get("/hello/{name}")asyncdefsay_hello(name:str):return{"message":f"Hello,{name}!"}if__name__=="__main__":importuvicorn uvicorn.run("main:app",host="127.0.0.1",port=8000,reload=True)

3.2 逐行拆解

  • app = FastAPI():创建应用实例,路由、文档都挂在它身上。
  • @app.get("/"):装饰器是 Python 的语法糖,等价于root = app.get("/")(root),作用是把函数注册进路由表。当GET /请求到来时,Starlette 查路由表,找到root函数并执行。
  • return {"message": "World"}:返回字典会被自动序列化为 JSON,并设置响应头Content-Type: application/json,你不用手动json.dumps。

【重点】装饰器路由是 FastAPI 的核心写法,本质就是"函数注册":@app.get("/路径")声明"什么请求路径、什么 HTTP 方法,交给哪个函数处理"。

3.3 路径参数与类型注解

/hello/{name}中的{name}是占位符。类型注解name: str有两个作用:

  1. 自动接收:访问/hello/FastAPI,函数里name就是字符串"FastAPI";
  2. 自动转换:如果注解是int,FastAPI 会把 URL 里的字符串转成整数,转换失败直接返回 422 错误。

四、Demo 02:请求体与 Pydantic —— 对接大模型接口

4.1 为什么需要 BaseModel

GET 请求的参数很简单,但 POST 通常传结构化的 JSON。手写校验长这样:

data=awaitrequest.json()prompt=data.get("prompt")ifnotisinstance(prompt,str):return{"error":"prompt 必须是字符串"}

用BaseModel一行类型注解就能替代上面全部代码。

4.2 完整代码(FastAPI + LangChain 调用 DeepSeek)

fromdotenvimportload_dotenv# 读取 .env 文件的工具importosfromfastapiimportFastAPIfrompydanticimportBaseModel# 数据校验基类fromlangchain_openaiimportChatOpenAI# 把 .env 里的键值对加载进环境变量load_dotenv()app=FastAPI(title="LangChain & FastAPI")# 初始化大模型客户端(DeepSeek 兼容 OpenAI 协议)llm=ChatOpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"),# 从环境变量读密钥base_url=os.getenv("DEEPSEEK_BASE_URL"),# 接口地址model=os.getenv("DEEPSEEK_MODEL"),# 模型名temperature=0.7,# 随机性,越大回答越发散)# 定义请求体模型:要求 JSON 里必须有 prompt 字段,且为字符串classChatReq(BaseModel):prompt:str# POST 接口:请求体经过 ChatReq 校验@app.post("/chat")asyncdefchat(req:ChatReq):resp=llm.invoke(req.prompt)# 调用大模型return{"input":req.prompt,"reply":resp.content}if__name__=="__main__":importuvicorn uvicorn.run("main:app",host="127.0.0.1",port=8000,reload=True)

4.3 关键步骤说明

  1. load_dotenv():把项目下.env文件的键值对注入环境变量,再用os.getenv()读取。密钥不写死在代码里,避免提交到 Git 泄露。
  2. ChatOpenAI(...):初始化一个"大模型客户端",之后llm.invoke("问题")就能拿到模型回答。
  3. class ChatReq(BaseModel):继承BaseModel后,ChatReq就是一个自带校验能力的请求模型,prompt: str表示必填且必须是字符串。
  4. req: ChatReq:FastAPI 看到参数类型是 Pydantic 模型,会自动完成"解析 JSON → 校验 → 实例化"三步。

4.4 校验效果

请求体结果
{"prompt": "你好"}通过,req.prompt = "你好"
{}422,缺少必填字段 prompt
{"prompt": 123}422,数字不能当作字符串
{"prompt": "hi", "extra": 1}通过,多余字段默认忽略

【核心原理】校验发生在你的函数执行之前。不合法的请求直接被挡在门外返回 422,你的函数拿到的req一定是干净可用的数据——这就是"类型即校验"的含义。

五、Demo 03:参数校验 —— Annotated + Path / Query

5.1 Annotated 是什么

Annotated是 Python 标准库typing提供的类型注解工具,作用是给类型附加"元数据"(额外信息):

Annotated[真实类型,元数据1,元数据2,...]

第一个参数是真正的数据类型,后面的元数据是附加说明。FastAPI 会读取这些元数据,把Path()、Query()的校验规则应用上去。

5.2 Path:给路径参数加校验

fromfastapiimportFastAPI,Path,QueryfromtypingimportAnnotated app=FastAPI()@app.get("/p/{article_id}")asyncdefarticle_detail(article_id:Annotated[int,Path(ge=2)]):# int:article_id 必须是整数(自动转换)# Path(ge=2):值必须 >= 2return{"article_id":article_id}

比较运算符的含义:

写法规则缩写含义
Path(ge=2)>= 2greater than or equal
Path(le=100)<= 100less than or equal
Path(gt=0)> 0greater than
Path(lt=100)< 100less than
Path(ge=2, le=100)2 ~ 100 之间组合使用

实际访问效果:

请求结果
/p/5通过,返回{"article_id": 5}
/p/1422,小于 2
/p/abc422,无法转成整数

【重点】为什么用Annotated而不是旧写法article_id: int = Path(ge=2)?因为新写法把"类型"和"规则"分开,语义清晰;必填参数也不会被误认为有默认值。

5.3 Query:查询参数校验

查询参数是 URL 问号后面的部分,多个参数用&连接:/article/list?page=2&size=5。

@app.get("/article/list")asyncdefarticle_list(page:Annotated[int,Query(1,ge=1)]=1,# 默认第 1 页,最小为 1size:Annotated[int,Query(10,ge=1)]=10,# 默认每页 10 条,最小为 1):return{"page":page,"size":size}
  • Query(1, ge=1):第一个参数1是默认值,ge=1是校验规则。不传page时自动用默认值。
  • 【易错点】原 Demo 里size写的是Query(10, ge=10),规则与默认值冲突——它意味着size最小只能是 10,传 5 会报错。如果业务本意是"每页最少 1 条",这是典型手误。校验规则必须和业务语义对齐。

5.4 三种参数工具对比

工具用在哪示例
Path()URL 路径参数/p/{id}Annotated[int, Path(ge=2)]
Query()查询参数?page=1Annotated[int, Query(1, ge=1)]
Field()请求体模型的字段Field(..., min_length=6)

一句话记忆:URL 上的用 Path / Query,JSON 请求体里的用 Field。

六、Demo 04:请求体字段校验 —— Field 与 model_dump

6.1 完整代码

fromfastapiimportFastAPIfrompydanticimportBaseModel,FieldfromtypingimportAnnotated app=FastAPI()# 商品模型:description / tax 是可选字段classItem(BaseModel):name:str# 必填description:str|None=None# 可选:str 或 None,默认 Noneprice:float# 必填,自动转浮点数tax:float|None=None# 可选@app.put("/items/{item_id}")asyncdefupdate_item(item_id:int,item:Item):# model_dump() 把模型实例转成普通字典,** 再展开合并进结果result={"item_id":item_id,**item.model_dump()}returnresult# 登录模型:Field 给字段加校验规则classLogin(BaseModel):# ... 表示必填email:Annotated[str,Field(...,description="邮箱地址")]password:Annotated[str,Field(...,min_length=6,max_length=20,description="密码,6~20位")]@app.post("/login")asyncdeflogin(data:Login):return{"email":data.email,"password":data.password}if__name__=="__main__":importuvicorn uvicorn.run("main:app",host="127.0.0.1",port=8000,reload=True)

6.2 可选字段的读法

description: str | None = None三段式读法:类型是str或None,默认值是None,即可以不传。而name: str没有默认值,就是必填。

6.3 Field:给字段加规则

Field是 Pydantic 提供的函数,给模型字段添加校验规则、默认值和说明。其中...是 Python 的省略号对象(Ellipsis),在Field里表示必填、无默认值。

Field(...)# 必填Field(None)# 可空,默认 NoneField(18,ge=0,le=150)# 默认 18,范围 0~150

常用参数:

参数作用
...必填
min_length/max_length字符串长度限制
gt/lt/ge/le数值范围
pattern正则表达式校验格式
description字段说明,显示在 /docs 文档里

6.4 model_dump 与字典展开

  • item.model_dump():把 Pydantic 模型实例转成普通字典(Pydantic v2 的方法,v1 时代叫.dict())。
  • {"item_id": item_id, **item.model_dump()}:**把字典展开,与前面的键值对合并成一个新字典。

【重点】model_dump在很多场景都会遇到,比如把大模型返回的消息对象转成字典存进对话历史——"对象变字典"是高频操作。

6.5 校验效果

请求体结果
{"email": "a@b.com", "password": "123456"}通过
{"password": "123456"}422,缺少必填的 email
{"email": "a@b.com", "password": "123"}422,密码少于 6 位

七、Demo 05:综合实战 —— Todo 待办接口

7.1 完整代码(修正版)

原 Demo 存在几个小问题(第十一节逐条分析),下面是修正后的完整可运行版本:

fromfastapiimportFastAPI,HTTPException,PathfrompydanticimportBaseModel,FieldfromtypingimportAnnotated,List app=FastAPI(title="Todo增删改查")todos=[]# 内存列表当"数据库"next_id=1# 自增 ID# 用户提交用的模型:没有 id,防止用户伪造编号classTodoCreate(BaseModel):title:Annotated[str,Field(min_length=1,max_length=100,description="待办标题,1~100个字符")]done:bool=False# 可选,默认未完成# 存储和返回用的模型:继承 TodoCreate,服务端补上 idclassTodo(TodoCreate):id:int# 创建待办@app.post("/todos",summary="创建待办",response_model=Todo)asyncdefcreate_todo(data:TodoCreate):globalnext_id# 声明修改模块级变量todo=Todo(id=next_id,**data.model_dump())# 服务端分配 idnext_id+=1todos.append(todo)returntodo# 查询所有待办@app.get("/todos",summary="查询所有待办",response_model=List[Todo])asyncdefget_all_todos():returntodos# 查询单个待办@app.get("/todos/{todo_id}",summary="查询指定ID待办",response_model=Todo)asyncdefget_todo(todo_id:Annotated[int,Path(gt=0,description="ID必须大于0")]):foritemintodos:ifitem.id==todo_id:returnitem# 没找到:返回标准的 404 错误,而不是 raise 一个字典raiseHTTPException(status_code=404,detail="Todo not found")if__name__=="__main__":importuvicorn uvicorn.run("main:app",host="127.0.0.1",port=8000,reload=True)

7.2 数据设计思路

  • todos列表充当"内存数据库",服务重启就清空——学习够用,生产环境要换成真正的数据库。
  • TodoCreate(用户提交,无 id)与Todo(服务端存储与返回,带 id)分成两个模型:id 由服务端分配,避免用户提交伪造的编号。Todo继承TodoCreate获得全部字段,再加一个id。
  • 模型里没写id却在路径参数上用Field校验是常见误用——路径参数应该用Path(对比表见 5.4 节)。

7.3 response_model:声明返回值结构

response_model=List[Todo]声明"这个接口返回的是 Todo 数组",有三大作用:

  1. 校验返回数据:函数返回后,FastAPI 按模型检查每一项,不符合直接报错,把问题拦在后端;
  2. 过滤多余字段:返回数据里模型之外的字段会被自动删掉,内部字段、敏感信息不会漏给前端;
  3. 生成响应文档:/docs 页面自动展示响应的 JSON 结构,前端不用问就知道格式。

summary="查询所有待办"则是纯文档用途,只显示在 /docs 的接口标题上,不影响任何逻辑。

7.4 查单条与 404

查单个待办用for循环匹配 id。找不到时必须返回 404 语义,标准做法是抛出HTTPException:

raiseHTTPException(status_code=404,detail="Todo not found")

【易错点】raise后面只能跟异常对象,不能 raise 一个字典(如raise {"detail": "..."}),那会直接抛TypeError。

八、async 异步:FastAPI 高性能的秘密

8.1 通俗理解

把服务器想象成奶茶店的服务员:

  • 同步(没有 async):接一单 → 站着等奶茶做完 → 才接下一单。等待期间什么都干不了,其他客人全在排队。
  • 异步(async):接一单 → 把单子递给后台 →转头继续接下一单。奶茶好了再回来取。

同步与异步处理请求的时间线对比:

同步:一个线程死等,其余请求全部排队 线程 ├──请求A──等待A(卡住)──完成A──请求B──等待B(卡住)──完成B──▶ ↑ A 在等待时,B 只能干等 异步:等待时让出控制权,一个线程穿插处理多个请求 线程 ├──请求A──让出去→处理B──返回A──完成A──返回B──完成B──▶ ↑ A 等待的时间被用来处理 B,总耗时大幅缩短

async def定义的函数就是"会安排事情的服务员":遇到耗时操作(数据库查询、文件读写、网络请求),先让出控制权去处理别的请求,结果好了再回来继续。

8.2 两种写法怎么选

函数写法FastAPI 怎么处理适用场景
async def跑在事件循环里,函数内可用await内部使用异步库(异步数据库驱动、httpx 等)
def(普通)自动丢到线程池执行,不会阻塞服务内部是同步阻塞调用(如 requests、同步 ORM)

选型原则一句话:函数里要await就写async def;全是同步阻塞调用就写普通def。两种 FastAPI 都能正确调度,不必强求全 async。

九、全文总结

本文沿一条主线走完了 FastAPI 的入门路径:Hello World(路由注册、路径参数)→ 请求体模型(BaseModel 对接大模型)→ 参数校验(Annotated + Path / Query)→ 字段规则(Field、model_dump)→ 综合实战(Todo 接口 + response_model)→ 异步原理(async / await)。

贯穿始终的核心思想只有一个:FastAPI 把路由、参数解析、数据校验、接口文档全部自动化,你只需要写业务函数本身。类型注解不再是"装饰",而是被框架真正消费的配置——写对类型,校验、转换、文档全部免费获得。

十、核心知识点复盘

知识点一句话要点
FastAPI 组成Starlette 负责 Web 底层与异步,Pydantic 负责类型校验
uvicornASGI 服务器,FastAPI 应用必须由它托管运行
装饰器路由@app.get("/路径")把函数注册进路由表
路径参数{name}占位符 + 类型注解自动转换
请求体模型继承BaseModel,字段类型即校验规则
Annotated给类型附加元数据,Annotated[int, Path(ge=2)]
Path / Query / FieldURL 路径参数 / 查询参数 / 请求体字段,各管一摊
...Field 里的省略号,表示必填
model_dump模型实例转字典(对象变字典)
response_model校验返回值、过滤多余字段、生成响应文档
HTTPException标准错误返回方式,如 404
async / await有异步库用async def,纯同步代码用def

祝你在 FastAPI 的路上一路畅通。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 2:52:18

读数据架构知识体系指南10数据建模方法(上)

1. 数据建模方法1.1. 数据建模是一种高级概念技术&#xff0c;用于设计数据库1.2. 涉及识别需要存储的数据&#xff0c;然后创建这些数据及其之间关系的结构化表示&#xff0c;并将其组织成表格和列1.3. 将这些表格和列视为数据库的逻辑表示&#xff0c;而存储在这些表格和列中…

作者头像 李华
网站建设 2026/10/1 2:52:23

HTML5 详解(二):拖拽、History、Geolocation 与全屏 API 实战指南

文档教程前端 【免费下载链接】Web 千古前端图文教程&#xff0c;超详细的前端入门到进阶知识库。从零开始学前端&#xff0c;做一名精致优雅的前端工程师。 项目地址&#xff1a; https://gitcode.com/gh_mirrors/we/Web 点击查看 免费下载 本篇文章是「千古前端图文教程」HT…

作者头像 李华
网站建设 2026/10/1 2:52:23

DeepSeek V4 技术解读:MoE 专家路由与负载均衡优化深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华