这一章不打算先把一堆工具名塞给你。我们要完成的是一个非常具体的结果:在一个干净的项目目录里准备 Python,隔离依赖,安装 FastAPI,写出两个接口,再亲眼看到服务器、响应和交互文档都正常工作。
第一次搭环境最容易出现的情况,是每条命令看起来都成功,启动时却提示找不到包;或者同一份代码在你的电脑上能跑,换到同事电脑就缺依赖。根源通常不是 FastAPI 本身,而是解释器、环境和依赖文件没有对齐。我们会把这三层逐一确认,再开始写代码。
本文以 Python 3.13、FastAPI 0.141.1 和 Uvicorn 0.52.1 完成整条流程。版本号会继续变化,你不必照抄小版本;真正要保留下来的是版本约束和锁定文件。
当前 FastAPI 要求 Python 3.10 或更高版本。新项目可以选择仍在维护的稳定版本;团队项目则先看仓库中的 requires-python、.python-version、持续集成配置和部署镜像,跟已有约束保持一致。版本越新不等于项目越稳,所有运行环境能使用同一条版本线才更重要。
先在终端执行:
python3 --versionPython 3.14.6Windows 上由官方安装器安装 Python 后,通常使用:
py --version如果 python3、python 和 py 中只有一个能用,不需要把它们全部修成同一个命令。你只需要找出哪个命令指向准备给项目使用的 Python,后续始终使用它。
还可以直接查看解释器路径:
python3 -c "import sys; print(sys.executable)"路径会指出真正运行代码的解释器。以后遇到“明明安装了包,导入却失败”,第一件事就是重新检查这条路径,而不是继续重复安装。
不要把操作系统自带的 Python 当成项目公共仓库。系统工具可能依赖它的包和版本,直接往里面安装 Web 项目依赖,既容易产生权限问题,也可能让两个项目互相覆盖依赖。
虚拟环境是项目专用的一套 Python 运行目录。它会记录自己的解释器入口和第三方包安装位置。激活环境后,终端会优先找到这个目录里的 python 和 pip。项目甲升级某个包,不会顺手改掉项目乙使用的版本。

在项目根目录创建环境:
python3 -m venv .venvmacOS 与常见 Linux shell 使用:
source .venv/bin/activateWindows PowerShell 使用:
.venv\Scripts\Activate.ps1激活不是一句仪式性的命令,它改变了当前终端查找可执行文件的顺序。验证比观察提示符更可靠:
python -c "import sys; print(sys.executable)"
python -m pip --version输出的两条路径都应包含项目的 .venv 目录。这里特意写 python -m pip,是为了让“运行 pip 的 Python”和“运行项目的 Python”明确绑定在一起。只写 pip 时,终端可能从别处找到另一个 pip。
结束工作后可以退出当前环境:
deactivate退出只会还原当前终端的路径,不会删除 .venv。下一次进入项目,重新激活即可。
把环境目录加入 .gitignore:
.venv/
__pycache__/
.pytest_cache/
.env.venv 体积大,里面还保存本机路径,既不适合提交,也不负责复现环境。真正应该提交的是依赖声明与锁定结果。
输入一条安装命令之后,包管理器会解析一棵依赖树。我们的代码使用 FastAPI 描述路由;FastAPI 使用 Starlette 处理 Web 层能力,使用 Pydantic 解析和校验数据;Uvicorn 则负责监听网络端口,把收到的请求交给应用。

这也解释了为什么只安装 fastapi 和安装 fastapi[standard] 得到的体验不同。核心包足够定义应用,但标准扩展会一并准备常见的服务器、命令行、表单解析、模板和开发辅助依赖。刚开始搭项目时,选择标准扩展可以减少“代码没问题,只是少装了运行工具”的干扰。
依赖名后面的方括号表示安装一组可选扩展,不是版本号。fastapi[standard] 仍然安装 FastAPI,只是同时选中了官方维护的标准依赖组。
传统的 venv + pip 很直接,适合先理解每一步发生了什么;uv 会把 Python 版本、虚拟环境、依赖声明、锁定和运行串成一套项目工作流。两条路线都能跑 FastAPI,真正容易出问题的是先用一个工具创建环境,又临时用另一个工具改依赖,最后没人知道哪个文件才是准的。
完成环境创建和激活后,执行:
python -m pip install "fastapi[standard]"再确认包确实装进当前解释器:
python -c 'import sys, fastapi, uvicorn; print(sys.executable); print(f"fastapi={fastapi.__version__}"); print(f"uvicorn={uvicorn.__version__}")'/private/tmp/fastapi-ch02.iZzmPF/.venv-pip/bin/python
fastapi=0.141.1
uvicorn=0.52.1第一行比后两行更关键:它证明当前命令使用项目环境中的解释器。如果那里显示系统目录,说明环境没有激活,或者你调用了错误的 Python。
小项目可以先把直接依赖写进 requirements.in:
fastapi[standard]安装时使用:
python -m pip install -r requirements.in需要保存当前环境中所有精确版本时,可以生成:
python -m pip freeze > requirements.txt然后在新环境中安装:
python -m pip install -r requirements.txtfreeze 会把直接依赖和间接依赖全部列出来,因此适合记录一套已经验证过的应用环境。不要把装过很多无关工具的公共环境拿来冻结,否则文件里会混入项目并不需要的包。
先确认 uv 可用:
uv --versionuv 0.9.27 (Homebrew 2026-01-26)在空目录中初始化项目,并指定要使用的 Python 版本线:
uv init --bare --python 3.13 .Initialized project `fastapi-ch02-izzmpf` at `/private/tmp/fastapi-ch02.iZzmPF`--bare 只创建最小的项目配置,不生成示例业务代码。接着添加依赖:
uv add "fastapi[standard]"这一步会同时做三件事:把直接依赖写入 pyproject.toml,解析精确版本到 uv.lock,并创建或同步项目的 .venv。关键配置会变成:
[project]
name = "fastapi-ch02-izzmpf"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"fastapi[standard]>=0.141.1",
]以后不要直接进入 uv 管理的环境再随手 pip install。正式依赖用 uv add,移除依赖用 uv remove,同步环境用 uv sync,在项目环境中运行命令用 uv run。这样 pyproject.toml、uv.lock 与 .venv 才会始终对应。
如果你只是跟着本课程搭项目,我建议后续示例使用 uv 路线。它省掉手动激活这一步,同时保留清楚的依赖声明和锁定结果。想练习标准库原生流程时,再用 venv 与 pip 重做一次,两者背后的隔离逻辑是相同的。
先让目录保持简单:
fastapi-learning/
├── .gitignore
├── .python-version
├── .venv/
├── main.py
├── pyproject.toml
└── uv.lock这里没有急着拆 routers、models、services。只有两个路由时,提前创建十几个空目录不会让结构更专业,反而会让第一次启动多出导入路径问题。等同类路由变多,再按职责拆包。
在 main.py 写入:
from fastapi import FastAPI
app = FastAPI(title="学习清单接口", version="0.1.0")
@app.get("/")
async def read_root():
return {"message": "FastAPI 已经运行", "docs": "/docs"}
@app.get("/tasks/{task_id}")
async
现在逐段看它做了什么。
app = FastAPI(...) 创建应用对象。Uvicorn 启动时要导入的就是它。title 和 version 描述的是我们自己的接口应用,会进入自动生成的接口说明。
@app.get("/") 把“根路径上的 GET 请求”交给紧随其后的函数。函数返回 Python 字典,FastAPI 会把它序列化为 JSON 响应,并设置合适的响应类型。
第二个路由同时演示了两类输入。task_id 出现在路径模板中,并标注为 int;detail 不在路径里,因此会被识别为查询参数。访问 /tasks/7?detail=true 时,FastAPI 会把字符串形式的 7 和 true 转换为 Python 的整数和布尔值,再调用函数。
你不需要为了“使用异步框架”而机械地把每个函数都写成 async def。这里使用异步函数,是为后面调用异步数据库或网络客户端预留一致写法。若函数内部调用的是只能同步等待的库,普通 def 也可以由 FastAPI 处理。真正要避免的是在 async def 内直接执行长时间阻塞操作。
先做一次纯导入检查,不启动端口:
uv run python -c 'import sys, fastapi, uvicorn; print(sys.version.split()[0]); print(f"fastapi={fastapi.__version__}"); print(f"uvicorn={uvicorn.__version__}")'3.13.11
fastapi=0.141.1
uvicorn=0.52.1这一步通过,说明解释器、框架和服务器依赖已经对齐;如果此时失败,问题还在环境层,不必急着检查浏览器。
在 main.py 所在目录执行:
uv run uvicorn main:app --reload --host 127.0.0.1 --port 8000INFO: Will watch for changes in these directories: ['/private/tmp/fastapi-ch02.iZzmPF']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process using WatchFiles
INFO: Started server process
INFO: Waiting for application startup.
INFO: Application startup complete.启动命令中最容易看错的是 main:app。冒号左边 main 是模块名,对应 main.py;右边 app 是模块里的变量名,对应 app = FastAPI(...)。它表达的其实就是“从 main 模块导入 app 对象”。
--reload 会观察代码文件,保存修改后重启开发进程。它适合本地开发,不是生产环境增加并发能力的开关。--host 127.0.0.1 只监听本机回环地址;--port 8000 指定端口。

保持服务器运行,另开一个终端请求根路径:
curl -sS http://127.0.0.1:8000/{"message":"FastAPI 已经运行","docs":"/docs"}再请求带路径参数和查询参数的路由:
curl -sS "http://127.0.0.1:8000/tasks/7?detail=true"{"task_id":7,"detail":true}这两次响应说明路由匹配、参数转换和 JSON 序列化都已经工作。此时打开 http://127.0.0.1:8000/docs,会看到可直接发请求的 Swagger UI;打开 http://127.0.0.1:8000/redoc,会看到另一套阅读型文档;http://127.0.0.1:8000/openapi.json 则提供生成这些页面所依据的 OpenAPI 描述。
检查文档和描述端点的状态:
curl -sS -o /dev/null -w "docs_status=%{http_code}\n" http://127.0.0.1:8000/docs
curl -sS -o /dev/null -w "openapi_status=%{http_code}\n" http://127.0.0.1:8000/openapi.jsondocs_status=200
openapi_status=200接口文档不是另一份需要手工同步的文件。FastAPI 会读取路由、参数类型和应用元数据,生成 OpenAPI 描述,再由文档界面展示出来。你把 task_id: int 改成其他类型,描述与校验行为会一起变化。
到这里,开发环境已经真的连成一条线:包管理器准备依赖,Uvicorn 监听端口,FastAPI 匹配路由并转换数据,函数返回结果,OpenAPI 描述又驱动交互文档。以后增加接口,仍然是在这条链路上扩展。
停止开发服务器时,在运行它的终端按 Ctrl+C。正常退出会显示应用关闭和进程结束信息,不需要另外查找并强制杀死进程。
环境问题让人烦躁,往往是因为我们盯着整条链路一起猜。更有效的办法是先看错误发生在哪一层:终端是否找到命令、Python 是否找到模块、Uvicorn 是否导入应用、端口是否能绑定。

常见提示是 command not found 或“无法将该项识别为命令”。先不要全局安装。使用项目工具显式运行:
uv run uvicorn main:app --reload使用 pip 路线时,先激活 .venv,再执行:
python -m uvicorn main:app --reloadpython -m uvicorn 绕过了 shell 对独立 uvicorn 可执行文件的查找,能明确使用当前 Python 中安装的模块。
如果写成不存在的模块:
uv run uvicorn missing:app --host 127.0.0.1 --port 8033ERROR: Error loading ASGI app. Could not import module "missing".依次检查:当前目录是否真的有 main.py,命令是否从项目根目录执行,文件名是否写错,代码包是否需要完整导入路径。假设应用移到 app/main.py,而 app 是 Python 包,导入串通常要改成 app.main:app。
右侧变量名写错时会得到另一种错误:
uv run uvicorn main:nope --host 127.0.0.1 --port 8033ERROR: Error loading ASGI app. Attribute "nope" not found in module "main".这说明 main.py 已经成功导入,问题缩小到对象名。检查文件里究竟写的是 app = FastAPI()、application = FastAPI(),还是一个返回应用的工厂函数,然后让冒号右侧与之对应。
同一地址和端口上已经有服务器时,再启动一个进程会失败:
uv run uvicorn main:app --host 127.0.0.1 --port 8000ERROR: [Errno 48] error while attempting to bind on address ('127.0.0.1', 8000): address already in use回到旧服务器的终端按 Ctrl+C,或者在确认旧进程仍需保留时换一个端口:
uv run uvicorn main:app --reload --port 8001先确认启动命令包含 --reload,再看 Uvicorn 输出的监听目录是否包含源码。如果你在容器、网络磁盘或某些子系统里开发,文件变化事件可能无法按默认方式传递,这时需要针对运行环境调整文件监听方式。不要用浏览器反复刷新来掩盖服务器根本没有重启的问题。
连续执行下面三条:
python -c "import sys; print(sys.executable)"
python -m pip --version
python -m pip show fastapi解释器路径、pip 所在路径和 FastAPI 的安装位置应该落在同一个 .venv 下。只要三者不在一起,就说明“安装依赖”和“运行项目”使用了不同环境。
不要在看到导入错误后立刻使用管理员权限全局安装。它可能暂时让错误消失,却把依赖装进另一套解释器,下一次打开终端、切换编辑器或部署时问题还会回来。
“我的环境现在能跑”和“任何人都能重建它”是两件事。.venv 证明前者;依赖声明、锁定文件和重建检查才证明后者。

pyproject.toml 描述项目接受的版本范围,uv.lock 保存解析出的精确版本和包文件校验信息。锁定文件应该提交到版本控制,.venv 不应该提交。
检查锁定文件是否仍与项目声明一致:
uv lock --checkResolved 45 packages in 3ms同步项目环境:
uv syncResolved 45 packages in 3ms
Audited 43 packages in 0.92ms在持续集成或部署流程中,如果不允许命令悄悄更新锁定结果,可以使用:
uv sync --locked当 pyproject.toml 与 uv.lock 不一致时,这条命令会失败,提醒你先在开发阶段明确更新锁定文件。这样部署拿到的是已经审查过的解析结果,不是现场重新选择一套新版本。
需要升级时也不要顺手删掉锁定文件。升级全部依赖可以显式执行 uv lock --upgrade;只升级一个包则使用 uv lock --upgrade-package 包名。升级后运行接口和测试,再提交新的锁定结果。
如果采用 pip 路线,可以把人工维护的直接依赖与完整锁定结果分开。requirements.in 表达“项目主动需要什么”,requirements.txt 表达“这次验证使用了哪些精确版本”。至少要确保生成锁定结果的环境只包含本项目依赖。
在一套全新环境里验证,而不是继续相信已经用很久的旧环境:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python -c "import fastapi, uvicorn; print(fastapi.__version__, uvicorn.__version__)"能在空环境中完成安装、导入和启动,才说明依赖文件足够复现。若安装依赖必须依靠某个人电脑上残留的包,锁定文件仍是不完整的。
终端跑通后,编辑器仍可能标红 from fastapi import FastAPI。这通常不是包没安装,而是编辑器选择了系统 Python。把项目解释器设置为 .venv/bin/python;Windows 对应 .venv\Scripts\python.exe。编辑器、终端、测试命令和部署入口指向同一套环境,类型提示与运行结果才会一致。
现在不要再增加工具,按顺序确认这些结果:
python 或 uv run python 显示的是项目选定的 Python 版本。.venv,并且 .venv/ 已加入忽略文件。main.py 中存在可导入的 app 对象。/docs 与 /openapi.json 都返回成功状态。如果这七项都成立,你得到的就不只是“今天碰巧能运行”的示例,而是一份可以继续写路由、加模型、接数据库并交给别人复现的项目起点。