
每次开新项目最先绊住我的往往不是业务逻辑而是Python环境本身。特别是用FastAPI写接口时依赖一多全局环境的pip list就会变成一锅粥。pydantic版本错位、uvicorn和其他ASGI服务互相干扰、本地跑得好好的代码换台机器就起不来这些场景我全遇过。所以现在我做FastAPI开发第一步永远是在VSCode里先把虚拟环境建好把解释器选对再谈其他。这篇内容就是一次完整的FastAPI开发环境搭建记录覆盖虚拟环境选型、VSCode解释器配置、FastAPI项目初始化、调试与CORS联调、依赖迁移这几个环节。适合刚开始用VSCode写Python的初学者也适合一直全局安装依赖、想规范项目环境的开发者作为参考。1. 为什么说虚拟环境是FastAPI开发的第一道防线1.1 没有隔离的依赖池有多痛我最早写Flask接口时项目A要用Flask 1.x项目B要用Flask 2.x。两个项目放在同一台机器上后安装的版本直接把前面的覆盖掉。结果就是项目A突然报AssertionError项目B的某个视图渲染不出来。查了一下午最后才发现是Flask版本被顶掉了。FastAPI对这种情况尤其敏感。它底层依赖pydantic做数据校验而pydantic v1和v2在API上差别很大。FastAPI在0.100.0版本之后默认使用pydantic v2但很多老项目、旧库还锁在pydantic v1。如果你在全局环境里装了一个依赖pydantic v1的包再跑FastAPI新项目可能连BaseModel的导入都会出问题。用一个生活化的类比来说不建虚拟环境就好比所有厨具调味料都堆在一个橱柜里。今天做川菜往里面放了花椒明天做粤菜怎么都找不回来食材本味因为上一道菜的味道还在锅里。虚拟环境就是每个项目一个独立灶台互不串味想用什么版本就用什么版本旧项目不会因为新项目装包而被破坏。1.2 venv、conda、uv 三种主流方案我建议怎么选目前最主流的方案有三个Python自带的venv、Anaconda系conda、以及最近几年很火的uv。我先放一张对比表方案创建命令适用场景特点venvpython -m venv .venv标准Python项目内置、体积小、无额外依赖condaconda create -n myenv python3.11数据科学、需要非Python原生库可管理Python版本和非Python依赖uvuv venv .venv追求速度、新项目Rust实现、下载快、锁文件精确对于FastAPI开发我的建议是如果只是纯Python接口项目直接选venv就够。它不需要额外安装工具Python装了就有创建出来的虚拟环境体积也不大。如果你的工作流里已经用Anaconda做数据分析那conda环境一样能在VSCode里被识别操作路径并没有变复杂。uv这个工具我最近几个项目都在用它最大的优势是快创建虚拟环境和安装依赖的速度比pip加venv的组合快一个数量级。锁文件机制也更可靠团队协作时能把依赖版本精确钉住避免「我本地能跑你本地跑不了」的问题。目前uv还在快速迭代部分老教程默认还是pip和venv读文档时留意版本差异就好。1.3 虚拟环境和Docker、Windows WSL的边界在哪里聊虚拟环境绕不开的一个问题是现在不是有Docker吗为什么还要在本地搞虚拟环境我的理解是Docker解决的是「整个运行环境的一致性」包括操作系统、系统库、Python版本、所有依赖全部打包虚拟环境解决的是「Python依赖层面的隔离」更轻量也更适合日常开发。拿我自己的习惯来说本地开发和调试直接用虚拟环境方便断点、热重载、随时改代码立即生效项目交付或部署时再打成Docker镜像保证线上环境和测试环境一致。两者不是替代关系而是不同阶段的不同工具。另外VSCode里用WSLWindows Subsystem for Linux做Python开发也常见。Windows上先在WSL里装好Python再在VSCode里通过「Remote - WSL」扩展连接进去一样能创建虚拟环境和跑FastAPI。好处是Linux环境的兼容性更好一些编译型依赖装起来不容易踩Windows坑。这套方案的配置路径和纯Windows差不多只是解释器路径对应的文件系统位置不同。2. VSCode里把解释器钉在虚拟环境上的完整过程2.1 创建虚拟环境之前先想清楚的两个问题很多人一上来就跑python -m venv .venv结果后面踩坑。我建议动手前先确认两件事。第一当前机器上的Python版本是否满足FastAPI要求。FastAPI最低要求Python 3.8新版本建议用3.10以上但这里不只是「能跑」的问题还要考虑你用不用新语法。比如我在示例里写的str | None这类类型注解是Python 3.10才引入的写法Python 3.9及以下跑不了。所以创建虚拟环境前先看一下python --version确认版本别建完环境再回来重新建。第二虚拟环境目录放哪里。我自己的习惯是统一放在项目根目录下命名为.venv。这样VSCode能自动发现.gitignore里也好处理整个环境跟着项目走删掉项目时环境也一并清掉不会在系统里留一堆残骸。2.2 在VSCode里创建并激活venv的步骤具体操作其实不复杂但有几处细节容易忽略。完整的流程我拆解如下在VSCode里打开项目文件夹按CtrlShiftP调出命令面板输入Terminal: Create New Terminal打开集成终端。在终端里执行python -m venv .venv。这里注意如果你的电脑装了多个Python版本建议直接用py -3.11 -m venv .venvWindows或python3.11 -m venv .venvmacOS/Linux来明确指定版本避免系统路径里的Python不是你想要的那一个。Windows下激活虚拟环境.venv\Scripts\activatemacOS/Linux下用source .venv/bin/activate。激活成功后终端提示符前面会多一个(.venv)前缀看到这个前缀就说明环境已激活。如果用的是uv创建命令会更快uv venv .venv然后uv pip install fastapi uvicorn。uv会直接管理虚拟环境并安装依赖不需要你先手动activate再pip install。有一个很容易栽跟头的点VSCode的Python扩展Microsoft官方那个会自动从项目根目录发现虚拟环境但它需要在python.terminal.activateEnvironment为true时才会在新开终端时自动激活。这个设置默认是开启的我建议你不要关掉它。2.3 让VSCode记住你的选择settings.json创建好虚拟环境后最关键的一步是让VSCode使用这个环境的解释器。最直接的方式是CtrlShiftP输入Python: Select Interpreter在弹出的列表里选择带.venv标识的那个解释器。它的路径通常会显示为.venv\Scripts\python.exeWindows或.venv/bin/pythonmacOS/Linux。但是手动选一次VSCode只会记住当前这次操作如果多人在同一个项目里协作别人打开这个项目时可能又指向系统Python。我推荐把选择固化到项目级的.vscode/settings.json里{ python.defaultInterpreterPath: ${workspaceFolder}/.venv/Scripts/python.exe, python.terminal.activateEnvironment: true, python.analysis.extraPaths: [./app] }${workspaceFolder}是VSCode内置的变量会动态替换为当前项目根目录这样项目换位置也不用改配置。补充的python.analysis.extraPaths是为了让Pylance认识自定义模块路径涉及多模块项目时很有用。这个文件跟着项目走团队成员拉取代码后就能直接用同一个解释器路径省去了一一指导的麻烦。2.4 终端激活失败的几种坑我把常见的虚拟环境激活问题整理成一张表方便对照排查错误现象常见原因解决办法.venv\Scripts\activate执行失败提示脚本被禁止运行PowerShell执行策略限制运行Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser终端里python仍指向全局Python激活命令没执行或路径写错手动执行激活命令确认.venv目录存在VSCode右下角解释器显示为全局解释器没有执行Select Interpreter或settings.json里defaultInterpreterPath没生效重新选择解释器或重启VSCode新建终端后没有自动进入虚拟环境python.terminal.activateEnvironment被手动关闭在settings.json里设为true其中执行策略那个坑第一次遇到时我还有点慌以为是环境建坏了。其实不是环境的问题是PowerShell出于安全考虑默认禁止运行.ps1脚本而VSCode默认终端就是PowerShell所以Windows用户很容易撞上。按上面表格里的命令执行一次就好了这个设置是针对当前用户的不是全局的可以放心改。3. FastAPI项目从初始化到跑通API的实操记录3.1 项目结构设计与依赖清单虚拟环境就绪之后我一般会先搭一个和实际项目接近的骨架而不是直接在根目录写个main.py草草了事。一个相对合理的最简结构是这样的fastapi-demo/ ├── .venv/ ├── .vscode/ │ └── settings.json ├── app/ │ ├── __init__.py │ ├── main.py │ └── routers/ │ └── __init__.py ├── requirements.txt └── .gitignoreapp/目录用来放业务代码routers/是路由模块后续项目变大了可以把用户、商品、订单等接口拆到不同文件里。如果你只是做最小Demo也可以先不用routers目录但从第一天就养成拆分习惯后面重构成本会低很多。依赖安装直接在已激活的虚拟环境里执行pip install fastapi uvicorn这里补充一下FastAPI和uvicorn的关系。FastAPI本身是一个Web框架负责路由、参数校验、依赖注入这些事真正监听端口、接收HTTP请求并转发给FastAPI的是uvicorn这个ASGI服务器。简单理解就是FastAPI是厨房负责把菜品做好uvicorn是服务员负责把菜端到顾客桌上两者配合才能完成一次完整的请求处理。3.2 最小可运行示例与启动参数说明我在app/main.py里写了一个最小示例几乎是所有FastAPI教程的起点from fastapi import FastAPI app FastAPI(titleFastAPI Demo) app.get(/) def read_root(): return {message: Hello FastAPI} app.get(/items/{item_id}) def read_item(item_id: int, q: str | None None): return {item_id: item_id, q: q}第二段路由是我特意加上的因为它展示了FastAPI的一个核心特性item_id被声明为int当你访问/items/abc时FastAPI会直接返回一个422校验错误而不是让视图函数内部去做类型转换。很多刚开始接触FastAPI的朋友会忽略这个特性其实这正是它高效的地方——参数校验交给框架做代码自然就干净了。启动服务的命令是uvicorn app.main:app --reload --host 0.0.0.0 --port 8000app.main:app的格式是「模块路径:应用变量名」。--reload是开发模式下的热重载开关代码文件一保存服务就自动重启改完代码不用手动停服务再启动。--host 0.0.0.0的意思是监听所有网卡地址这样局域网内其他设备也能访问到你的开发服务如果只在本机调试不加这个参数也没有问题。3.3 通过自动文档接口验证环境服务起来以后在浏览器里访问http://127.0.0.1:8000/docs会看到一个Swagger UI页面。这个页面是FastAPI根据代码中的类型注解自动生成的里面列出了所有路由、请求参数、响应模型还能直接点击「Try it out」按钮发请求测试接口。我第一次用FastAPI时印象最深的就是这个自动文档。以前用Flask写接口要维护一份Postman集合或者写一堆API文档接口一改文档就过期。FastAPI是代码即文档类型注解和路径装饰器就是文档本身。除了/docs/redoc还会生成另一个风格的文档页面用哪个看个人习惯我日常调试用/docs多一点。3.4 加一个带请求体的POST接口试水看完GET接口我建议你再写一个POST接口这样能把FastAPI的请求体校验彻底跑通。在main.py里加一段from pydantic import BaseModel class Item(BaseModel): name: str price: float is_offer: bool False app.post(/items/) def create_item(item: Item): return {name: item.name, price: item.price, is_offer: item.is_offer}在/docs页面里打开/items/接口直接发送一段JSON{ name: 键盘, price: 199.0 }FastAPI会把JSON解析成Item实例并自动校验字段类型。如果你把price传成abc响应会直接给出422错误并指明是哪个字段校验失败。这种体验比自己在视图函数里手写if not isinstance(...)清爽太多。走到这一步说明你已经能在VSCode的虚拟环境里完整开发FastAPI接口了。4. 调试配置与热重载开发效率翻倍的两个开关4.1 launch.json里配置FastAPI调试VSCode里按CtrlShiftD进入运行和调试面板点击「创建launch.json」选择「Python Debugger」类型里的「FastAPI」模板VSCode会自动生成一个调试配置。它生成的实质内容等价于下面这段{ version: 0.2.0, configurations: [ { name: FastAPI Debug, type: debugpy, request: launch, module: uvicorn, args: [app.main:app, --reload], jinja: true, justMyCode: true } ] }这里关键的字段是module和args。module: uvicorn表示以模块方式启动uvicorn等价于在终端执行python -m uvicorn。args里的--reload会沿用到调试模式。配置好后按F5就能直接在VSCode里启动FastAPI服务并且可以在Python代码里打断点、查看变量、单步执行。要注意的是新版Python扩展用的调试器是debugpy老教程里写的type: python已经是过时写法如果你照着旧文章配置会找不到调试器类型。4.2 热重载和断点调试我建议分开用这里分享一个我踩过不少坑的经验调试状态下的--reload和断点之间会互相打架。具体表现是--reload模式其实是uvicorn在后台开了一个子进程来运行你的应用当保存代码触发重载时子进程会被重启已经命中的断点也随着进程消失。有时候断点命中了但变量面板还没来得及刷新进程又被重载了。我现在的习惯是调试的时候把args里的--reload去掉需要频繁改代码看效果的时候用终端跑带--reload的启动命令而不是F5调试。一句话总结调试用F5改代码看效果用终端热重载两者不要混在同一个任务里。另外还要提醒一下端口冲突的问题。如果上一个服务没有完全退出再启动时会报address already in use。遇到这个提示先检查是不是有残留的uvicorn进程或者直接换个端口测试。在Windows上我偶尔会遇到明明关了终端进程还在后台占着端口的情况打开任务管理器把Python进程结束掉就好。4.3 Pylance和类型检查的配合VSCode里装好「Python」扩展后默认的语言服务是Pylance。它最大的价值不是自动补全而是类型检查。把鼠标悬停在item_id: int这样的变量上Pylance会提示类型信息FastAPI的泛型标注也基本都能正确识别。对FastAPI项目我推荐在settings.json里把类型检查模式调到basic{ python.analysis.typeCheckingMode: basic }strict模式下Pylance会检查得非常细对老项目不太友好新项目倒是可以从一开始就放开。这个配置配合函数返回类型标注能减少很多低级错误尤其是字段名写错、参数传错这类问题。很多时候我还没启动服务Pylance已经把代码里的类型不匹配标红了省去了启动后才发现报错的时间。4.4 环境变量在调试场景下的管理FastAPI项目跑起来通常需要读一些配置比如数据库连接串、密钥、接口地址。在VSCode里调试时环境变量怎么注入是一个实际的问题。我的做法是借助.env文件配合VSCode Python扩展的envFile功能。在调试配置里加一行{ envFile: ${workspaceFolder}/.env }这样F5启动时VSCode会自动加载项目根目录下.env文件里的变量。在代码里用os.getenv(DATABASE_URL)或者pydantic-settings的SettingsConfigDict(env_file.env)就能读取到。注意.env文件要加进.gitignore避免把密钥提交到仓库里。5. 接口联调阶段的CORS与代理配置5.1 浏览器跨域报错的本质FastAPI接口写好后下一步基本就是和前端联调。现在大多数项目都是前后端分离前端Vue开发服务器跑在http://localhost:5173后端FastAPI跑在http://localhost:8000。这种情况下前端页面里的fetch或axios请求后端接口时浏览器会先发起一个预检请求OPTIONS如果后端没有返回正确的跨域响应头浏览器就会拦截这个请求控制台里报出一串CORS错误。很多初学者看到CORS就发怵其实拆开看并不复杂。CORSCross-Origin Resource Sharing是浏览器的一种安全机制只要请求的来源协议、域名、端口任一不同和当前页面不一致浏览器就默认不允许读取响应。前端在5173端口后端在8000端口端口不一致所以算跨域。这是开发环境里最常见的CORS来源。5.2 用CORSMiddleware解决开发环境的跨域FastAPI解决这个问题非常顺手内置了CORSMiddleware中间件。在main.py里加几行配置就行from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[ http://localhost:5173, http://127.0.0.1:5173, ], allow_credentialsTrue, allow_methods[*], allow_headers[*], )allow_origins要特别注意开发环境用[*]确实省事但生产环境千万别这么写。allow_origins的作用是告诉浏览器「来自这个来源的请求可以读取响应」如果设成*等于任何网站都能向你的接口发请求并读取结果。而且allow_origins[*]和allow_credentialsTrue不能同时生效浏览器会直接拒绝这次请求。实际项目中allow_origins应该填前端上线后的具体域名。5.3 用开发代理绕过CORS更贴近生产行为这里再提供一个替代思路。前端框架的开发服务器一般自带代理功能以Vite为例可以在vite.config.ts里配置代理让前端请求/api开头的路径时由Vite开发服务器转发到FastAPI的8000端口export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8000, changeOrigin: true, }, }, }, });这样对浏览器来说请求还是发到5173端口同源所以没有跨域问题后端代码里可以不配置CORS。我个人做前后端联调时更倾向于这个方案因为它更贴近生产环境的行为生产环境通常由Nginx这类反向代理统一分发请求开发环境和生产环境行为越接近上线的意外就越少。不过用代理方案要求后端路由统一挂在一个前缀下比如/api这也是一种合理的接口设计规范。5.4 联调时最常见的接口报错排查顺序联调阶段报错我建议按照这个顺序排查能省下不少时间。先看网络面板里请求是否真的发出去了如果状态是(failed) net::ERR_CONNECTION_REFUSED一般是后端没启动或者端口不对和后端代码无关。再看状态码404一般是路径写错重点检查路由前缀有没有重复比如后端已经是/api/items前端又拼了一次/api。如果状态码是422那是FastAPI参数校验没过切换到/docs里用同样的参数试一次看是不是参数名或类型对不上。如果是CORS报错检查后端allow_origins有没有包含前端实际请求的来源。按这个顺序走下来绝大多数联调问题都能在几分钟内定位而不是靠猜。6. 依赖管理与环境迁移的几个实际问题6.1 requirements.txt与uv.lock哪个更适合你的项目项目开发到一定阶段依赖管理的问题就会浮出水面。最传统的方式是pip freeze requirements.txt这个命令会把当前虚拟环境里所有已安装的包及其版本号全部导出。但它有一个缺点会包含那些间接依赖——你并没有直接引用、只是某个包安装时自动带进来的库。如果哪天某个间接依赖升级或者在其他系统上版本号解析行为不同项目可能就会出现「在我这里好好的到你那就报错」的情况。如果用的是uv创建的环境更推荐用uv lock生成的uv.lock文件。它的特点是全量锁死了所有包的精确版本和依赖树uv sync一下就能恢复出一模一样的环境。缺点是目前uv的普及度还在爬坡如果和团队同事协作需要确认大家都愿意用它。我的选择标准是小项目、个人项目直接用requirements.txt足够团队项目或对可复现性要求高的直接用uv更稳妥。两者并不互斥uv pip freeze也能导出标准的requirements.txt给不用uv的同事用。6.2 无网络电脑怎么搭建虚拟环境这个场景我在实际工作里遇到过公司内网机器不能连外网但开发任务又必须在上面完成。解决办法其实不复杂核心思路是「在能联网的机器上把依赖包下载好拷贝过去离线安装」。具体步骤是这样先在联网机器上创建一个同Python版本的虚拟环境执行pip download把依赖包全部下载到一个文件夹pip download -r requirements.txt -d ./offline_packages然后把整个offline_packages文件夹拷贝到内网机器在内网机器上创建好虚拟环境后这样安装pip install --no-index --find-links./offline_packages -r requirements.txt--no-index的意思是不要访问PyPI--find-links指定从本地目录找安装包。注意pip download时除了会下载requirements.txt里列出的包还会一并把它们的依赖拉下来所以离线安装时基本不会缺包。如果你用的是uv对应命令是uv pip install --offline -r requirements.txt。另外还有一个细节如果内网机器上连Python本身都没装那先得把Python安装包带进去。这时候用Anaconda或Miniconda反而更方便因为它们的安装包是自包含的可以离线安装并直接生成一个基础环境。这也是为什么有些企业内网项目组习惯用conda的原因之一不是因为它多先进而是离线场景下它更省事。6.3 虚拟环境整体迁移的可行方案除了离线装包有时候你还需要把整个虚拟环境从一台电脑搬到另一台电脑。如果两台机器的操作系统一致、Python版本一致最简单的方式是直接打包.venv目录。Windows下可以用tar命令macOS/Linux下也可以用tar -czf venv_backup.tar.gz .venv在目标机器上解压到相同路径然后在VSCode里重新选择一次解释器即可。但这里有个前提虚拟环境里的脚本通常带有硬编码路径最好不要改项目目录位置否则启动时可能找不到Python解释器。如果两台机器的Python版本不一致或者操作系统不同就不要尝试直接搬迁了老老实实用requirements.txt重新安装更可靠。6.4 把环境配置写进项目模板一劳永逸最后聊一个「偷懒」的思路。环境配置这种事情每做新项目都从头来一遍确实繁琐。我现在会把.vscode/settings.json、.gitignore、requirements.txt甚至是main.py骨架做成一整套模板放到自己的Git仓库或者本地的模板目录里。新项目直接copy一份改个项目名就能开工。如果你团队里有统一的脚手架工具也可以把这套环境配置沉淀到脚手架里新成员入职之后拉取项目、创建虚拟环境、安装依赖、按F5启动一条龙下来不用翻文档。最后说点我自己的体会环境配置这件事我觉得最值得花时间的地方不在「把环境跑起来」而在「让别人和环境本身不再给你添乱」。用虚拟环境隔离项目依赖用settings.json固定解释器用锁文件固定依赖版本这些动作看起来都小但每一条都能省掉未来的某个深夜排查。最后再分享一个小习惯每次搭好FastAPI项目环境我会先把.gitignore写好确保.venv和__pycache__不会被提交到仓库然后跑一遍pip freeze requirements.txt把初始依赖固定住之后每次新增依赖都顺手更新这个文件。前期多花十分钟后面能少折腾好几个下午。