首页/Python 框架指南/FastAPI 入门与 ASGI
Python 框架指南 · 第 01 章

FastAPI 入门与 ASGI

本系列是一本「轻量用法手册」:每章只认识 FastAPI 生态里的一个零件,讲清楚它是什么、怎么装、怎么跑、文档去哪看,不搭建贯穿全课程的大工程。示例数据统一来自虚构的内容平台「墨集 inkhub」——栏目、文章、作者…

约 22 分钟8,830 字13 个代码示例

本系列是一本「轻量用法手册」:每章只认识 FastAPI 生态里的一个零件,讲清楚它是什么、怎么装、怎么跑、文档去哪看,不搭建贯穿全课程的大工程。示例数据统一来自虚构的内容平台「墨集 inkhub」——栏目、文章、作者,仅作数据演示。

读完本章你会:拿到一张框架选型对比表、听懂 WSGI/ASGI 这对名词、装好 Uvicorn、跑起来第一个服务、打开 /docs 玩一圈、给 Pydantic 留个第一印象。

1. 先摊开地图:Django、Flask、FastAPI 怎么挑

是什么

三者都是用 Python 写 Web 服务的框架,但性格完全不同。用装修打个比方:

  • Django(2005 年生):整装交付的精装大平层。家具、家电、保安(ORM、后台管理、用户认证)全给你配齐,拎包入住;代价是想砸一面墙,得先弄清整栋楼的水电走向。
  • Flask(2010 年生):一间毛坯房加基础水电。框架只给最核心的一小坨,瓷砖、灯具自己挑,自由度最高,腿也跑得最勤。
  • FastAPI(2018 年生):专为「外卖档口」设计的小店。动线全围绕出餐(API)优化——类型标注一写,参数校验、接口文档全都自动跟上。

一张表横向看:

对比维度 Django Flask FastAPI
出场年份 2005 2010 2018
性格 大包大揽,什么都要自己管 极简内核,按需外挂 为 API 而生,类型驱动
速度体感(Hello World) 约 5k req/s 约 9k req/s 约 30k req/s
上手难度 陡峭:概念多、体量大 平缓:核心直观 中等:得先习惯类型标注与异步
顺手的场景 大型站点、CMS、自带后台的项目 小工具、快速原型、高度定制 高性能 API、微服务、给 AI 模型开接口
代表用户 Instagram、Pinterest Netflix、Airbnb Uber、Microsoft、Netflix(部分业务)

怎么选:一句话——要后台管理选 Django,要轻巧灵活选 Flask,要做 API 选 FastAPI。本课程从这章起全程 FastAPI。

怎么看文档

  • Django 官网:https://www.djangoproject.com/
  • Flask 官网:https://flask.palletsprojects.com/
  • FastAPI 官网(有中文版):https://fastapi.tiangolo.com/

2. WSGI 与 ASGI:前台和后厨的通话规范

是什么

先解决一个困惑:你在浏览器敲下 http://127.0.0.1:8000/columns,这串请求本质是原始字节流;而你的 Python 函数拿到的是规整的参数、返回的是 dict。中间的「翻译 + 体力活」由谁干?

答案是分工:

  • Web 服务器:干体力活——守端口、收字节、拆报文、送响应。好比餐厅的前台。
  • Web 应用(框架):干脑力活——对路由、校验数据、跑业务、拼结果。好比餐厅的后厨。

前台和后厨怎么对话?得先约好一套「通话规范」。Python 社区先后定过两版:

规范 定位 工作模式 一个比喻
WSGI 早期标准 同步 + 多线程 传统小馆:一个服务员从头盯到尾一桌,接待量靠多雇人(线程)堆出来
ASGI 现代标准 异步 + 事件循环 网红店:一个机灵服务员满场穿梭,几十桌同时照看,谁举手先应谁

一句话记住:WSGI 是「一桌一服务员」的世界,ASGI 是「一人看全场」的世界。高并发、WebSocket、长连接这些新需求,都得靠 ASGI 才好使。

2.1 ASGI 服务器(前台)具体在忙什么

  • 守着端口,接收客户端连接,读写原始字节流
  • 把 b'GET /columns/1 HTTP/1.1\r\n...' 这样的字节拆成结构化的 method、path、headers
  • 处理 HTTP/1.1、HTTP/2、WebSocket 的协议细节(分块传输、Keep-Alive……)
  • 启动并维护事件循环,管理进程与线程

2.2 ASGI 应用(后厨)具体在忙什么

  • 路由匹配、数据校验、执行业务逻辑、组装响应
  • 常见 ASGI 应用框架:FastAPI(底层是 Starlette)、Django(新版本同样支持)、Starlette 本身

2.3 双方如何对接:接头暗号是 app

规范要求:ASGI 应用必须由 ASGI 服务器启动,且对外暴露一个可调用对象 app,签名固定:

app(scope, receive, send)
# scope:   dict,本次连接的「订单小票」——type(http / websocket / lifespan)、path、method、headers 等
# receive: 异步函数,await receive() 拿到下一段「原料」(请求体 / 客户端消息)
# send:    异步函数,await send(message) 把做好的「菜」递出传菜口(响应头 / 响应体)

平时写 FastAPI 你几乎碰不到这三个参数——框架已经替你包好了。但排查部署问题、读框架源码时,认得这层接头暗号会轻松很多。

2.4 一单请求的完整旅程

  1. Uvicorn 在 8000 端口守门
  2. 客人进门:请求字节流到达
  3. 前台拆包:把字节流解析成 scope 小票,备好 receive / send 两个对讲频道
  4. 前台喊单:调用 app(scope, receive, send)
  5. FastAPI 拿小票对路由,找到对应的灶台(你写的处理函数)
  6. 灶台按需取原料(await receive())、做菜、递菜(await send())
  7. 前台把菜打包成完整的 HTTP 响应,字节流写回 socket,客人收货

2.5 常见 ASGI 服务器速查

服务器 一句话画像 协议覆盖 什么时候会想到它
Uvicorn uvloop + httptools 双引擎 HTTP/1、WebSocket(HTTP/2 实验性) FastAPI 圈默认款,本课程全程用它
Hypercorn 协议覆盖最全的全能型 HTTP/1、HTTP/2、HTTP/3、WebSocket 要上 HTTP/2 / HTTP/3 时
Daphne Django Channels 官方配套 HTTP/1、HTTP/2、WebSocket Django 异步项目
Granian Rust 实现,性能狂热派 HTTP/1、HTTP/2、WebSocket 追求极限吞吐的生产环境
Gunicorn 老牌 WSGI 服务器,可挂 ASGI worker HTTP/1 想要「多进程 + ASGI」混搭部署时
NGINX Unit 多语言通吃的应用服务器 HTTP/1、HTTP/2 一个入口管理多语言应用时

怎么看文档:ASGI 规范见 https://asgi.readthedocs.io/ ,属于兴趣加餐,不要求一次读完。

3. Uvicorn:给应用装上发动机

是什么

Uvicorn 是目前最流行的 ASGI 服务器。打个比方:FastAPI 是车身,Uvicorn 是发动机——车身再漂亮,没发动机点不着火。第 2 节里的 app,最终就是被 Uvicorn 拉起来跑的。

怎么装

uv add fastapi "uvicorn[standard]"

两个细节:

  • uvicorn[standard] 的 [standard] 叫 extras(可选依赖组),勾选后会额外装上 uvloop、httptools 等加速件,性能明显更好,日常直接带上
  • 两侧引号别省:zsh 等终端会把方括号当特殊字符解析,加引号最稳妥

怎么跑(三种姿势,按需取用)

姿势一:脚本内嵌启动(本章 demo 的做法,最省心):

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

姿势二:命令行启动(「模块路径:应用变量」定位):

uv run uvicorn demo.demo_app:app --reload

姿势三:官方脚手架命令(自带热重载):

uv run fastapi dev demo/demo_app.py

生产环境再加码:--host 0.0.0.0 对外开放,--workers 4 多进程吃满多核。注意 --reload 与 --workers 互斥,前者只留给开发期。

常用参数速查:

参数 作用 建议值
--host 监听地址 本机调试 127.0.0.1;对外 0.0.0.0
--port 监听端口 8000
--reload 代码一改自动重启 仅开发期
--workers 工作进程数 生产环境 2 ~ CPU 核数

怎么看文档:https://www.uvicorn.org/ ,全部配置项在「Settings」一节。

4. 第一个应用:墨集开门营业

怎么装:命令同上一节;若本章目录还不是 uv 工程,先 uv init 再 uv add。

怎么跑

uv run demo/demo_app.py

终端出现 Uvicorn running on http://127.0.0.1:8000 就是点着火了。

代码全景(节选自 demo/demo_app.py,完整文件可直接运行):

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI(title="墨集 inkhub", version="0.1.0")


class ArticleCreate(BaseModel):
    """创建文章的请求体"""

    title: str = Field(min_length=1, max_length=30, examples=["夜航船里的旧灯"])
    column_id: int = Field(ge=1, description="文章所属的栏目编号")
    tags: list[str] = Field(default_factory=list, examples=[["散文", "夜记"]])


@app.get("/columns/{column_id}", summary="栏目详情")
async def get_column(column_id: int) -> dict[str, object]:
    ...  # 按编号查找栏目并返回


@app.post("/articles", summary="创建文章")
async def create_article(article: ArticleCreate) -> dict[str, object]:
    ...  # article 已通过校验,此处用内存列表代替真实落库

逐行拆解

  • app = FastAPI():造出一个符合 ASGI 规范的可调用对象,变量名习惯叫 app——正好对上第 2 节的接头暗号
  • @app.get("/columns/{column_id}"):挂招牌,HTTP 方法 + 路径一一对应;{column_id} 是路径里的占位坑
  • column_id: int:一行类型标注,就是 FastAPI 的「魔法开关」——框架自动把路径字符串转成 int,转不动(比如传 abc)直接回 422
  • 请求体:参数标成 Pydantic 模型 article: ArticleCreate,框架自动解析 JSON 并校验
  • 返回值:dict / list 原样返回,框架自动打包成 JSON 响应

怎么看效果:浏览器挨个访问

地址 看点
http://127.0.0.1:8000/ 首页欢迎语
http://127.0.0.1:8000/columns 栏目列表
http://127.0.0.1:8000/columns/1 栏目详情;把 1 换成 abc 体验 422
http://127.0.0.1:8000/articles?limit=1&keyword=伞 查询参数:limit 截断条数、keyword 按标题模糊匹配
http://127.0.0.1:8000/docs 交互式文档(下一节主角)
http://127.0.0.1:8000/redoc 只读文档
http://127.0.0.1:8000/openapi.json 机器可读的接口总账

怎么看文档:https://fastapi.tiangolo.com/zh/ ,中文版教程质量很高,跟着翻一遍胜过十篇二手博客。

5. /docs、/redoc、/openapi.json:自动印好的说明书

是什么

三个地址,三份「自动生成的产品说明书」:

  • OpenAPI 规范:用一份标准 JSON 描述整套 API 的行业规范。FastAPI 启动时扫描全部路由和类型标注,自动生成 openapi.json 并挂在 /openapi.json——这就是接口的「机器可读总账」
  • Swagger UI(/docs):能试吃的柜台。点「Try it out」填参数、发请求、看真实响应,开发调试期的主力
  • ReDoc(/redoc):精装印刷的菜谱。三栏排版、滚动到哪渲染到哪,上百个接口的大文档也不卡;适合对外发布接口文档
对比点 Swagger UI(/docs) ReDoc(/redoc)
定位 交互调试 阅读查阅
Try it out 支持,页面上直接发请求 不支持,纯只读
界面 功能全,配置项多 简洁清爽,长文档加载快
适用阶段 开发自测 对外发布

怎么让文档更好看:路由装饰器上直接加文案:

@app.get(
    "/columns",
    summary="栏目列表",
    description="返回墨集当前全部栏目,含名称与一句话简介",
    response_description="栏目数组",
)
async def list_columns() -> list[dict[str, object]]:
    ...

怎么关掉它们:文档本质也是路由,把地址设成 None 就摘牌:

app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)

生产环境常见写法是按环境变量动态决定:

import os

is_production = os.getenv("INHUB_ENV") == "prod"

app = FastAPI(
    docs_url=None if is_production else "/docs",
    redoc_url=None if is_production else "/redoc",
    openapi_url=None if is_production else "/openapi.json",
)

怎么看文档:路由级配置项大全见 https://fastapi.tiangolo.com/zh/tutorial/path-operation-configuration/ ;OpenAPI 规范本身见 https://www.openapis.org/ 。

6. Pydantic 初见:给数据过安检

是什么

Pydantic 是一个「数据建模 + 校验 + 转换」三合一的库,FastAPI 的地基之一。把它想象成海关安检通道:

  • 字符串数字 "101" 进来,整数 101 出去(转换)
  • 缺斤少两、超长越界的字段,当场拦下并开出明细单(校验 + ValidationError)
  • 出关之后,代码里拿到的一定是干净、类型正确的数据(建模)

怎么装:不用单独装——uv add fastapi 时它作为底层依赖已经进包了。

怎么跑:uv run demo/demo_pydantic.py,纯脚本、不起服务,直接在终端看结果:

from datetime import datetime

from pydantic import BaseModel, Field, ValidationError


class Author(BaseModel):
    author_id: int = Field(ge=1, description="作者编号,从 1 开始")
    pen_name: str = Field(min_length=1, max_length=12, examples=["沈墨"])


class Article(BaseModel):
    article_id: int = Field(default=0, ge=0)
    title: str = Field(min_length=1, max_length=30)
    published_at: datetime | None = Field(default=None)
    author: Author
    tags: list[str] = Field(default_factory=list)


raw = {
    "article_id": "101",                 # 字符串数字
    "title": "夜航船里的旧灯",
    "published_at": "2026-08-27 09:30",  # 字符串时间
    "author": {"author_id": "7", "pen_name": "沈墨"},
    "tags": ["散文", "夜记"],
}

article = Article(**raw)  # 安检通过:类型全部转换到位
print(article.article_id, article.published_at)
print(article.model_dump())  # 导出为普通字典

故意塞一份违规数据,看它怎么拦:

bad = {
    "article_id": -1,  # 违反 ge=0
    "title": "",       # 违反 min_length=1
    "author": {"author_id": 0, "pen_name": "沈" * 20},  # 违反 ge=1 与 max_length=12
}

try:
    Article(**bad)
except ValidationError as exc:
    for error in exc.errors():
        print(error["loc"], "->", error["msg"])  # 哪个字段、什么原因,逐条列出

Field:给字段做精装修。裸类型标注只能管「是或不是」,Field 能管「到什么程度」:

class Column(BaseModel):
    column_id: int | None = Field(
        default=None,      # 默认值
        title="栏目ID",     # 文档里的字段标题
        description="栏目的唯一编号,新建时留空由系统分配",
        ge=1,              # 必须 ≥ 1
        examples=[1, 2],   # 文档里展示的示例值
    )

    name: str = Field(
        ...,               # 三个点 = 必填
        title="栏目名称",
        min_length=2,
        max_length=12,
        examples=["深夜书房"],
    )

它和 FastAPI 的三点关系(后面章节会反复用到):

  1. 请求体声明成 Pydantic 模型后,校验全自动——字段越线直接回 422,错误明细就是 errors() 那个格式,一行 if 都不用你写
  2. 响应同样可以用模型约束输出结构(第 2 章展开)
  3. 模型的字段、约束、示例全部进 openapi.json,/docs 里的表单就是照着模型渲染出来的

怎么看文档:https://docs.pydantic.dev/latest/ ,Field 的全部约束参数在「API → Fields」页。

7. 收摊小结

  • 选型口诀:要后台管理找 Django,要轻巧灵活找 Flask,要做 API 用 FastAPI
  • WSGI 是同步多线程规范,ASGI 是异步规范;服务器干体力活、应用干脑力活,接头暗号是 app(scope, receive, send)
  • Uvicorn 是发动机:uv add fastapi "uvicorn[standard]" 一条命令备齐全家
  • 类型标注是 FastAPI 的魔法开关:参数转换、请求校验、自动文档,全都从这一行标注开始
  • /docs 用来试吃,/redoc 用来阅读,/openapi.json 是机器可读的接口总账
  • Pydantic 是数据安检机:转换脏数据、拦下违规数据,errors() 会把原因逐条列清楚

8. 自测清单

  1. 用自己的话向同事讲清 WSGI 和 ASGI 的分工差异
  2. Uvicorn 和 FastAPI 各扮演什么角色?写好的 app 为什么自己跑不起来?
  3. /docs 与 /redoc 分别适合什么阶段使用?openapi.json 是谁、在什么时机生成的?
  4. 浏览器访问 /columns/abc 时发生了什么?谁发现的问题、谁返回的 422?
  5. Pydantic 在 FastAPI 里承担哪几件事?ValidationError 的 errors() 返回什么结构?
  6. 动手:把 demo 服务的端口改成 9000,用「脚本内嵌」和「命令行」两种姿势分别启动验证

下一章:02 · 路由与请求响应