云计算百科
云计算领域专业知识百科平台

我学 FastAPI 的一些心得:从零到跑通第一个接口

最近在学 FastAPI,感觉这个东西真的挺有意思的。之前我用过 Flask,写接口是挺简单,但每次都要自己搞数据校验、写文档,麻烦得很。后来听朋友推荐 FastAPI,说它"快、自动生成文档、类型提示很爽",我就试着学了一下。这篇文章算是我自己的学习笔记吧,尽量用大白话把我踩过的坑和搞懂的东西分享出来。

一、FastAPI 是个啥?

简单说,FastAPI 就是一个用 Python 写 Web API 的框架,底层用的是 Starlette(负责网络部分)和 Pydantic(负责数据校验)。它最大的特点就是利用 Python 的类型提示,让你写函数的时候顺便把参数类型标一下,它就能自动帮你做数据校验、类型转换,甚至生成接口文档。

对我来说最吸引我的点:

  • 不用手写文档:写完代码直接打开 /docs 就能看到一个可以交互的 API 文档页面,还能直接在上面测试接口,太省事了。

  • 性能好:官方说性能可以跟 Node.js、Go 比,虽然我没实际测过,但用起来确实感觉响应很快。

  • 语法简单:写起来跟 Flask 差不多,定义路由就是加个装饰器,对新手很友好。

  • 数据校验强:比如你定义参数是 int,用户传了 abc,它会自动返回 422 错误,不用自己判断。

所以我觉得 FastAPI 特别适合写小型 API 或者快速做原型,配合自动文档,前后端联调的时候能省很多沟通成本。

二、我的第一个 FastAPI 程序

1. 环境准备

我是在本地用虚拟环境跑的,建议你也这么做,避免装一堆包把系统 Python 搞乱。

python -m venv venv

然后激活:

  • Windows: venv\\Scripts\\activate

  • Mac/Linux: source venv/bin/activate

2. 安装

安装 FastAPI 和一个服务器。FastAPI 本身不带服务器,需要用 ASGI 服务器跑起来,最常用的是 uvicorn。

pip install fastapi "uvicorn[standard]"

那个 [standard] 会装一些额外依赖,比如性能优化,建议加上。

3. 写代码

新建一个 main.py,内容很简单:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}

解释一下:

  • FastAPI() 创建应用实例。

  • @app.get("/") 表示这是一个 GET 请求,路径是根路径。

  • 函数里 return 一个字典,FastAPI 会自动转成 JSON 返回。

4. 跑起来

在终端执行:

uvicorn main:app –reload

  • main 是文件名(不含 .py)

  • app 是里面那个实例变量

  • –reload 是热重载,改代码自动重启,开发的时候很方便。

启动后会看到:

INFO: Uvicorn running on http://127.0.0.1:8000

浏览器打开 http://127.0.0.1:8000/ 就能看到 JSON 返回。

重点来了:打开 http://127.0.0.1:8000/docs,你会看到一个 Swagger UI 的界面,上面有你刚才写的接口,点一下 "Try it out" 就能直接测试。我第一次看到这个的时候真的觉得挺爽的,再也不用单独用 Postman 了。

三、路由和路径参数

路由是啥?

路由就是告诉 FastAPI:当用户访问某个 URL 并且用某个 HTTP 方法请求时,应该执行哪个函数。FastAPI 用装饰器来定义:

  • @app.get() -> GET 请求

  • @app.post() -> POST 请求

  • @app.put() -> PUT 请求

  • @app.delete() -> DELETE 请求

比如:

@app.get("/items")
def list_items():
return ["item1", "item2"]

访问 /items 就会执行这个函数。

路径参数:URL 里带变量

路径参数就是 URL 路径中的一部分是可变的,比如 /items/123 里的 123。你可以在路径中用 {参数名} 表示,然后在函数参数里用同样的名字接收。

基础用法

@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id, "type": str(type(item_id).__name__)}

这里我特意返回了 type,就是想验证一下类型转换。你访问 http://127.0.0.1:8000/items/42,返回:

{
"item_id": 42,
"type": "int"
}

看到没,item_id 已经是整数 42,不是字符串 "42"。这就是类型提示的作用:FastAPI 自动把路径参数转成了你指定的类型。

如果你访问 /items/abc,它会返回 422 错误,因为 abc 没法转成 int。这个错误信息还很详细,会告诉你哪个字段类型不对。我用 Flask 的时候得自己写一堆判断,现在完全不用管。

多个路径参数

可以定义多个:

@app.get("/users/{user_id}/posts/{post_id}")
def get_user_post(user_id: int, post_id: int):
return {"user_id": user_id, "post_id": post_id}

访问 /users/1/posts/99 就会返回:

{
"user_id": 1,
"post_id": 99
}

一个我踩过的坑:固定路由和动态路由顺序

刚开始学的时候,我写了一个 /users/me 和 /users/{user_id},结果访问 /users/me 一直报 422 错误。后来才发现是路由顺序问题。

FastAPI 会按照代码里定义的顺序依次匹配。如果你把 /users/{user_id} 写在前面,那么访问 /users/me 时,me 会被当成 user_id 的值,然后因为类型是 int 而转换失败。

正确做法:把固定路由 /users/me 写在动态路由 /users/{user_id} 前面。

@app.get("/users/me")
def read_current_user():
return {"user_id": "the current user"}

@app.get("/users/{user_id}")
def read_user(user_id: int):
return {"user_id": user_id}

这样 me 会被固定路由捕获,其他数字才会走动态路由。这个坑我觉得新手很容易踩,所以专门提一下。

用枚举限制路径参数的取值

有时候你希望路径参数只能是几个固定值,比如模型名称。可以用 Python 的 Enum 来定义。

from enum import Enum

class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"

@app.get("/models/{model_name}")
def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}

这样用户只能传 alexnet、resnet、lenet,传别的会返回 422。我用这个特性做了一个简单的模型选择接口,感觉很实用。

路径参数包含斜杠的情况

如果你的路径参数本身要包含 /,比如文件路径,可以用 :path 转换器。

@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"file_path": file_path}

访问 /files/images/photo.jpg,file_path 就会是 images/photo.jpg。这个适合做文件下载或者读取目录结构。

不过要注意,这种"贪婪"的参数最好放在路由末尾,不然会把后面的路由都吃掉。

四、动手练一练

建议你自己试着加一个路由:

@app.get("/greet/{name}")
def greet(name: str):
return {"message": f"Hello, {name}!"}

然后访问 http://127.0.0.1:8000/greet/FastAPI,会返回:

{
"message": "Hello, FastAPI!"
}

再去 /docs 页面里玩一玩,输入不同的名字,看看响应变化。这样你就能感受到自动文档的好处了。

最后

FastAPI 给我的感觉就是:你写代码的时候把类型写清楚,它就能帮你做很多事。路径参数、查询参数、请求体这些,都可以通过类型提示自动校验和转换,而且文档是自动生成的,省了不少事。

我目前只学了路由和路径参数,后面还要学查询参数、请求体、Pydantic 模型这些。等学完了再继续分享。

如果你也在学 FastAPI,欢迎一起交流,或者直接去看官方文档,写得挺清楚的。

完整示例代码

from fastapi import FastAPI
from enum import Enum

app = FastAPI()

class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"

@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}

@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id, "type": str(type(item_id).__name__)}

@app.get("/users/me")
def read_current_user():
return {"user_id": "the current user"}

@app.get("/users/{user_id}")
def read_user(user_id: int):
return {"user_id": user_id}

@app.get("/models/{model_name}")
def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}

@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"file_path": file_path}

运行:

uvicorn main:app –reload

然后打开 http://127.0.0.1:8000/docs 玩起来吧

赞(0)
未经允许不得转载:网硕互联帮助中心 » 我学 FastAPI 的一些心得:从零到跑通第一个接口
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!