环境搭建的目标:十分钟内得到一个可热重载运行的 FastAPI 开发服务器,并且理解每一步为什么这样做——虚拟环境隔离什么、标准安装与精简安装差在哪、uvicorn 与 Flask 内置服务器在开发体验上的差异。本节同时给出 venv、virtualenv、conda 三套隔离工具的对比结论。
阅读完本节,你应当能够:
fastapi[standard] 与 fastapi[all] 两个安装项的差别;先看问题再谈工具。Python 的包默认装进解释器的全局 site-packages,如果机器上项目 A 依赖 pydantic 1.x、项目 B 依赖 2.x,第二次安装会把第一次覆盖掉,两个项目只能活一个。虚拟环境的思路很朴素:给每个项目复制(或链接)一份独立的解释器环境,包装在这份环境里,互不可见。
这也是 FastAPI 项目必须用虚拟环境的原因之一:Pydantic v1 与 v2 不兼容(第 2 章会详细对比),不同教程、不同存量项目对版本的要求五花八门,没有隔离就等着互相踩。
三套主流工具的对比:
| 工具 | 来源 | 特点 | 适合 |
|---|---|---|---|
| venv | Python 自带 | 零安装,功能够用 | 绝大多数项目的默认选择 |
| virtualenv | 第三方 | 比 venv 快,支持旧版 Python | 旧版本解释器或多版本切换频繁 |
| conda | Anaconda 生态 | 连 Python 解释器本身也隔离,可装非 pip 包 | 数据科学依赖(NumPy 一族)、科学计算环境 |
我的建议:纯 Web 项目直接 venv,不引入额外概念;团队里如果数据科学家和后端共用环境,conda 才有必要。uv 等新一代工具近年也流行起来,它把虚拟环境、依赖解析、锁定文件合成一个命令,速度快得多,新建项目可以优先考虑,但理解 venv 的原理仍是前提——uv 的行为模型是在它之上的加速。
以 venv 为例,Windows 与 macOS 的命令差异只在激活一步。
创建并激活:
# 两个平台通用:在项目根目录创建 .venv 文件夹 python -m venv .venv # Windows 激活 .venv\Scripts\activate # macOS 与 Linux 激活 source .venv/bin/activate
激活成功后命令行前会出现 (.venv) 前缀,此后 pip install 装的包只进这个环境。
隔离的原理值得一提,因为它解释了后面很多现象:虚拟环境并没有复制整个解释器,它只是建了一个包含独立 site-packages 与一个指向基础解释器的启动器的目录,激活脚本做的事也仅是把该目录的路径放到命令查找顺序的最前面。所以环境本身极轻,每个项目一个毫无压力;也因此删掉环境文件夹等于删掉全部依赖,重建只需一条安装命令配合依赖清单——这也是接下来强调"固化依赖"的原因。
安装 FastAPI 有两个档位,这是新手最容易困惑的点:
# 完整档:含 uvicorn 服务器、rich 增强报错等一组推荐配套 pip install "fastapi[standard]" # 最小档:只装框架本体,不含服务器与部分可选依赖 pip install fastapi
最小档装完是不能直接跑的——fastapi dev 命令与 uvicorn 都不在。什么时候用最小档?生产镜像里你可能想精确控制服务器实现(比如换 hypercorn),或者只要框架来跑测试,此时逐项安装 fastapi uvicorn 更可控。日常开发直接完整档,省心。
历史包名说明:早期教程里的 fastapi[all] 是旧写法,覆盖面与现在的 standard 档接近,读到时不必纠结。
最小可运行的应用只有几行:
from fastapi import FastAPI app = FastAPI(title="对比实验应用") @app.get("/") def root(): return {"framework": "fastapi", "version": "0.1"}
启动开发服务器有两种方式。完整档安装后自带 CLI:
fastapi dev main.py
或者显式用 uvicorn(更通用,也是生产部署的基本形态):
uvicorn main:app --reload --port 8000
main:app 的意思是"main 模块里的 app 变量"。--reload 开启热重载:保存文件后服务器自动重启,改一行代码刷新浏览器就能看到效果。
这里有一个值得对比的细节:Flask 的 flask run 也有热重载,但 Flask 内置服务器调试模式经常顺带开启交互式调试器(出错页面可以直接执行代码),本地方便,忘了关就是事故。FastAPI 的 reload 只是重启进程,出错时返回标准 500 响应加终端里的堆栈,心智负担更接近生产环境的行为。这个差异不大,但体现了两个框架对"开发态与生产态距离"的不同态度。
浏览器打开本地回环地址的 8000 端口根路径,应返回那段 JSON;再访问同一端口下的 docs 路径,能看到自动生成的交互式文档——第一次亲眼看到"代码即文档",值得花两分钟玩一下:展开接口、点 Try it out、执行,看真实响应。

FastAPI 要求 Python 3.8 以上,实际开发建议 3.10 起步:3.8 已停止安全维护,而且新语法(X | None 可选类型写法、match 语句)在 3.10 后才可用,类型标注的简洁程度差别明显——对 FastAPI 这种签名即契约的框架,标注写起来顺不顺手直接影响开发体验。
一个真实的坑:Windows 上 PowerShell 执行激活脚本可能被策略拦下,报"禁止运行脚本"。解法是以管理员身份执行 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser,或改用 cmd / Git Bash 激活。另一个常见问题是多 Python 共存的机器上 python 命令指向老版本,用 python --version 确认,必要时用 py -3.12 -m venv .venv 显式指定版本。
装完框架只是工具链的一半,另一半是让编辑器理解你的类型标注。装好 Python 插件的 VS Code 会自动用 Pylance 做类型检查,你在签名里写了 item_id: int,后面误把它当字符串拼接时编辑器立刻飘黄——这套反馈回路与 FastAPI 的运行时校验形成双保险:编辑期拦住开发者自己,运行期拦住外部输入。Flask 项目里这种编辑期反馈要弱得多,因为参数从 request 对象动态取出,静态分析工具无从推断类型。这是"类型驱动"在开发体验层面的隐性红利,值得在搭环境的第一天就配好。
推荐的最小配置:编辑器开启严格度适中的类型检查、安装 ruff 做风格与导入检查、命令行保留一个随时可跑的自检脚本。三者都不改变框架行为,但决定了长期维护这个项目的舒适度。
环境能跑之后,马上要养成固化依赖的习惯。最低限度:
pip freeze > requirements.txt
freeze 输出的是当前环境的全量精确版本,能复现但夹杂传递依赖,可读性差。更规范的做法是手写一个只列直接依赖的 requirements(如 fastapi、uvicorn、sqlalchemy),让解析器去解决传递依赖,CI 里再配合锁定工具。传统上这个位置属于 pip-tools,现在 uv 的 uv lock 是更快的替代。对比一下两种文件的定位:直接依赖清单是给人看的意图,锁定文件是给机器看的快照,两者都要,别混用。
既然本节的主题是工具链对比,有必要把"承载应用的进程"这一层也摊开。开发期几乎无脑选 uvicorn:它基于 uvloop 与 httptools,性能在 ASGI 服务器里长期处于第一梯队,文档与生态也最全。但生产上你会遇到另外两个名字。
hypercorn 支持 HTTP/2 与更完整的协议特性集,如果服务需要原生的服务器推送或更细的 worker 配置策略,它是 uvicorn 的正规替代。granian 是用 Rust 写的新生代,多 worker 场景的内存占用与启动速度有优势,但资料存量还薄,出问题时排查成本高一些。三者的关系类似于 Flask 世界里 gunicorn 一家独大对比 ASGI 世界的多选项竞争——WSGI 时代协议简单所以实现趋同,ASGI 时代协议能力多(WebSocket、HTTP/2、寿命管理),实现之间的取舍空间自然更大。
另一类常见搭配是"gunicorn 作为进程管理器加 uvicorn 的 worker 类":用 gunicorn 成熟的进程守护、优雅重启机制去管理多个 uvicorn 工作进程。这套组合在传统运维体系(systemd 加负载均衡)下很顺,但在容器环境里显得多余——容器编排器本身就负责进程守护与重启,镜像里直接跑单个 uvicorn 进程、一个容器一个进程,反而更符合十二要素应用的原则。这两种部署形态的完整对比放在第 7 章,这里只需建立印象:开发用 uvicorn 加热重载,生产在"进程管理器加 worker"与"单进程容器"之间按运维体系选择。
装完环境后,别急着进入业务开发,先用一个固定的检查集确认工具链每一环都正常。下面这个应用覆盖了路由、类型校验、自动文档三个关键面:
from fastapi import FastAPI, Query app = FastAPI(title="环境自检") @app.get("/ping") def ping(): return {"pong": True} @app.get("/echo/{count}") def echo(count: int): return {"count": count} @app.get("/search") def search(q: str, limit: int = Query(5, ge=1, le=20)): return {"q": q, "limit": limit}
依次访问三个路径:第一段确认服务器与路由通了;第二段故意传一个非数字,应得到带 loc 与 input 字段的 422 结构,说明 Pydantic 验证在工作;第三段把 limit 传成 0 或 99,观察范围约束同样生效。最后打开交互式文档,确认三个接口与约束都出现在文档里。四步全过,说明从类型提示到验证到文档生成的整条链路都在位——这套检查集也可以在升级依赖版本后重跑一遍,作为最快的回归验证。
顺带对比一下传统的验证方式在这套检查下会是什么表现:Flask 版本里 request.args.get 取 limit 后手写范围判断,漏写一个分支就是线上漏洞,而且文档页面上不会出现任何约束提示——前端只能靠口口相传。同一个检查集跑在两种框架上,差异一目了然,这也是我们把环境自检设计成"三接口"而不是"一个 hello world"的原因:hello world 只能验证安装成功,验证不了框架的核心机制是否真正为你工作。
fastapi[standard] 开发省心,最小档生产可控;旧写法 fastapi[all] 语义接近 standard。fastapi dev 是封装好的开发入口,uvicorn main:app --reload 是通用形态,后者也是理解生产部署的基础。环境就绪,第 2 章进入正题:路径参数、查询参数、请求体在 FastAPI 里如何用一套签名语法统一表达,并与 Flask 的手写解析逐项对照。