想把 FastAPI 项目打包成一个 exe 发给非技术用户,直接:
pyinstaller -F main.py
跑一下崩得七七八八。因为 uvicorn 走的是字符串式导入,PyInstaller 环境下拿不到;再加上一堆子模块(uvicorn.loops.*、starlette.*)静态分析捞不全。
正确起步:写个 start.py
不要直接打 main.py,写个显式导入 app 对象的启动文件:
# start.py
from main import app # 直接拿对象,不用字符串
import uvicorn
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
对应的 main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"hello": "world"}
打包 start.py:
pyinstaller -F start.py
Could not import module "main" 的原因
如果 start.py 里写:
uvicorn.run("main:app", host="0.0.0.0", port=8000)
main:app 是字符串,uvicorn 会在运行时 importlib.import_module("main")。PyInstaller 打包后 main 不在 sys.path 里,直接崩:
Error loading ASGI app. Could not import module "main"
改成直接 import + 传对象最省事:
from main import app
uvicorn.run(app, ...)
或者告诉 PyInstaller 也带上 main:
pyinstaller -F start.py --hidden-import=main
uvicorn 子模块丢失
跑起来抛:
ModuleNotFoundError: No module named 'uvicorn.loops'
uvicorn 有一堆按需加载的 loop / protocol / logger 子模块,PyInstaller 全漏了。一次性带上:
pyinstaller -F start.py \
--collect-all fastapi \
--collect-all uvicorn \
--collect-all starlette \
--collect-all pydantic
--collect-all X = 把 X 及其所有子模块、数据文件、二进制全打进去。
静态文件 / 模板
用了 StaticFiles 或 Jinja2Templates 得加数据:
Windows:
pyinstaller -F start.py ^
--add-data "templates;templates" ^
--add-data "static;static"
Linux/macOS:
pyinstaller -F start.py \
--add-data "templates:templates" \
--add-data "static:static"
代码里读路径要兼容 PyInstaller 临时目录:
import sys, os
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
def base_path():
return getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
app.mount("/static", StaticFiles(directory=os.path.join(base_path(), "static")), name="static")
templates = Jinja2Templates(directory=os.path.join(base_path(), "templates"))
后台运行、无 CMD 窗口
想双击不弹黑框:
pyinstaller -F -w start.py --collect-all uvicorn ...
-w 关控制台。注意:关了控制台,print / uvicorn 的日志就看不到了,改成写日志文件:
uvicorn.run(
app,
host="0.0.0.0",
port=8000,
log_config={
"version": 1,
"handlers": {
"file": {"class": "logging.FileHandler", "filename": "app.log"},
},
"root": {"handlers": ["file"], "level": "INFO"},
},
)
完整命令模板
一次搞定:
pyinstaller -F -w start.py ^
--name myapi ^
--icon app.ico ^
--add-data "templates;templates" ^
--add-data "static;static" ^
--collect-all fastapi ^
--collect-all uvicorn ^
--collect-all starlette ^
--collect-all pydantic
产物:dist/myapi.exe,双击就是一个后台服务。
什么时候别打包
- 生产环境线上服务:Docker / systemd 更好
- 要热更新:exe 每次改代码都要重打
- 依赖 GPU / CUDA:打包体动辄几百 MB
- 多用户 / 高并发:exe 里塞 uvicorn 效率一般
PyInstaller 更适合:内网小工具、给客户发一个”点两下就跑”的本地 API、演示 demo。
一句话总结
写 start.py + 直接 import app + --collect-all 四个关键字。字符串导入是坑,把 uvicorn 家族收全就通了。
