MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python通过FastAPI实现高性能RESTful API

2023-07-242.2k 阅读

1. 理解 RESTful API

1.1 RESTful API 概念

REST(Representational State Transfer)是一种软件架构风格,由 Roy Fielding 在 2000 年的博士论文中提出。RESTful API 则是遵循 REST 原则设计的 API,它基于 HTTP 协议,利用 HTTP 方法(GET、POST、PUT、DELETE 等)来操作资源,使得不同系统之间能够通过标准的接口进行交互。

例如,一个简单的博客系统,文章可以被视为一种资源。我们可以使用 GET 方法来获取文章内容,使用 POST 方法来创建新文章,PUT 方法用于更新文章,DELETE 方法删除文章。每个文章可以有一个唯一的标识符(比如文章 ID),通过这个标识符可以对特定的文章资源进行操作。

1.2 RESTful API 的优势

  • 易于理解和使用:RESTful API 基于 HTTP 协议,而 HTTP 是广泛使用且被熟知的协议。开发人员无论是前端还是后端,都对 HTTP 方法(GET、POST 等)有基本的了解,这使得 API 的使用和维护都更加容易。
  • 可扩展性:RESTful 架构允许轻松添加新的资源和操作。由于每个资源都有自己独立的 URL,添加新资源只需要定义新的 URL 和相应的操作即可,不会对现有系统造成太大影响。例如,在上述博客系统中,如果要添加评论功能,只需要定义评论资源的 URL(如 /articles/{article_id}/comments)以及相关的操作(如 POST 方法创建评论)。
  • 跨平台和语言无关性:因为 RESTful API 使用标准的 HTTP 协议,所以不同平台(如 Windows、Linux、iOS、Android 等)和编程语言(如 Python、Java、JavaScript 等)都可以轻松地与 RESTful API 进行交互。这使得系统可以方便地与各种不同技术栈的外部系统集成。

2. FastAPI 简介

2.1 FastAPI 是什么

FastAPI 是一个基于 Python 的现代、快速的 Web 框架,用于构建 API。它基于 Python 的类型提示功能,结合了 Flask 和 Starlette 的优点。FastAPI 旨在让开发人员能够快速、高效地开发出健壮的 API。

2.2 FastAPI 的特点

  • 速度快:FastAPI 基于 Starlette 构建,Starlette 是一个高性能的 ASGI(Asynchronous Server Gateway Interface)框架。它使用异步编程,能够处理大量并发请求而不会阻塞线程,从而大大提高了 API 的性能。
  • 类型提示:FastAPI 充分利用 Python 3.5 引入的类型提示功能。通过在代码中明确指定参数和返回值的类型,不仅可以提高代码的可读性,还能在开发过程中利用 IDE 的类型检查功能,更早地发现错误。例如:
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

read_item 函数中,item_id 被明确指定为 int 类型,q 被指定为 str 类型且为可选参数。这样,在开发过程中,如果传递给 item_id 的值不是整数,IDE 或者类型检查工具可以及时发现并提示错误。

  • 自动生成文档:FastAPI 会根据代码中的定义自动生成交互式的 API 文档,包括 Swagger UI 和 ReDoc。这些文档不仅提供了 API 的详细说明,还允许用户直接在文档界面中测试 API 端点。这对于开发人员之间的协作以及向外部开发者提供 API 接口非常方便。例如,启动上述 FastAPI 应用后,访问 http://127.0.0.1:8000/docs 就可以看到 Swagger UI 文档,访问 http://127.0.0.1:8000/redoc 可以看到 ReDoc 文档。

3. 安装 FastAPI 及相关依赖

要开始使用 FastAPI,首先需要安装它以及相关的依赖包。FastAPI 依赖于 uvicorn 作为 ASGI 服务器,pydantic 用于数据验证和解析。可以使用 pip 进行安装:

pip install fastapi uvicorn pydantic

uvicorn 是一个轻量级、高性能的 ASGI 服务器,能够充分发挥 FastAPI 的异步特性。pydantic 则负责对传入的数据进行验证和解析,确保数据符合我们在代码中定义的类型。

4. 创建第一个 FastAPI 应用

4.1 基本的 FastAPI 应用结构

下面是一个简单的 FastAPI 应用示例,它定义了一个根路径(/)的 GET 请求处理函数:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

在这个示例中,首先从 fastapi 模块导入 FastAPI 类,然后创建一个 FastAPI 实例 app。使用 app.get 装饰器定义了一个处理根路径 GET 请求的函数 read_root,该函数返回一个包含 "Hello": "World" 的 JSON 响应。

4.2 运行 FastAPI 应用

要运行这个 FastAPI 应用,可以使用 uvicorn 命令。在终端中进入包含上述代码的文件所在目录,然后执行以下命令:

uvicorn main:app --reload

这里 main 是包含 FastAPI 应用的 Python 文件名(如果文件名是 main.py),appFastAPI 实例的名称。--reload 选项用于在代码发生变化时自动重新加载应用,方便开发过程中的调试。

启动成功后,在浏览器中访问 http://127.0.0.1:8000/,就可以看到返回的 "Hello": "World" 响应。

5. 定义 API 端点

5.1 不同 HTTP 方法的端点

FastAPI 可以很方便地定义不同 HTTP 方法的 API 端点。除了 GET 方法,常见的还有 POST、PUT、DELETE 等方法。以下是一个示例,展示了如何定义这几种不同方法的端点:

from fastapi import FastAPI

app = FastAPI()

# GET 请求,获取所有项目
@app.get("/items/")
def read_items():
    return [{"name": "Item 1"}, {"name": "Item 2"}]

# POST 请求,创建新项目
@app.post("/items/")
def create_item(item: dict):
    return {"message": "Item created successfully", "item": item}

# PUT 请求,更新项目
@app.put("/items/{item_id}")
def update_item(item_id: int, item: dict):
    return {"message": f"Item {item_id} updated successfully", "item": item}

# DELETE 请求,删除项目
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
    return {"message": f"Item {item_id} deleted successfully"}

在这个示例中:

  • read_items 函数处理 GET 请求,返回所有项目的列表。
  • create_item 函数处理 POST 请求,接收一个字典类型的 item 数据,并返回创建成功的消息和创建的项目。
  • update_item 函数处理 PUT 请求,需要路径参数 item_id 和一个字典类型的 item 数据,返回更新成功的消息和更新后的项目。
  • delete_item 函数处理 DELETE 请求,只需要路径参数 item_id,返回删除成功的消息。

5.2 路径参数和查询参数

5.2.1 路径参数

路径参数是 URL 路径中的一部分,用于标识特定的资源。在 FastAPI 中,可以在函数定义中通过在参数名前加上 {} 来定义路径参数。例如:

from fastapi import FastAPI

app = FastAPI()

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

在这个例子中,item_id 就是路径参数,并且被指定为 int 类型。当访问类似 /items/123 这样的 URL 时,123 就会被传递给 read_item 函数中的 item_id 参数。

5.2.2 查询参数

查询参数是 URL 中 ? 后面的部分,用于传递额外的信息。在 FastAPI 中,可以在函数定义中直接定义查询参数。例如:

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
def read_items(q: str = None):
    if q:
        return {"message": f"Searching for {q}"}
    else:
        return {"message": "No query parameter provided"}

在这个例子中,q 就是查询参数,并且是可选的(默认值为 None)。当访问 /items/?q=apple 这样的 URL 时,apple 就会被传递给 read_items 函数中的 q 参数。

6. 数据验证与序列化

6.1 Pydantic 模型

FastAPI 使用 Pydantic 进行数据验证和序列化。Pydantic 允许我们定义数据模型,通过这些模型可以对传入的数据进行验证,并将数据序列化为特定的格式。例如,定义一个用户模型:

from pydantic import BaseModel

class User(BaseModel):
    username: str
    email: str
    password: str

在这个例子中,定义了一个 User 类,它继承自 PydanticBaseModelUser 模型包含三个字段:username(字符串类型)、email(字符串类型)和 password(字符串类型)。

6.2 在 API 中使用 Pydantic 模型

可以在 API 端点中使用 Pydantic 模型来验证和解析传入的数据。例如:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    username: str
    email: str
    password: str

@app.post("/users/")
def create_user(user: User):
    return {"message": "User created successfully", "user": user.dict()}

在这个示例中,create_user 函数的参数 userUser 类型,即我们定义的 Pydantic 模型。当客户端发送 POST 请求到 /users/ 时,FastAPI 会根据 User 模型对请求体中的数据进行验证。如果数据不符合模型定义(比如缺少 email 字段或者 password 不是字符串类型),会返回错误响应。如果验证通过,user 参数将包含解析后的数据,并且可以通过 user.dict() 将其转换为字典形式返回。

7. 处理请求体和响应体

7.1 请求体

请求体通常用于 POST、PUT 等请求中,包含要发送到服务器的数据。在 FastAPI 中,可以使用 Pydantic 模型来定义请求体的结构。例如:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    description: str = None

@app.post("/items/")
def create_item(item: Item):
    return item

在这个例子中,Item 是一个 Pydantic 模型,定义了 name(字符串类型)、price(浮点数类型)和 description(字符串类型,可选)三个字段。create_item 函数接收一个 Item 类型的 item 参数,这个参数就是请求体数据。FastAPI 会自动将请求体数据解析为 Item 模型实例,并进行验证。

7.2 响应体

响应体是服务器返回给客户端的数据。FastAPI 会自动将函数的返回值转换为合适的响应体格式(通常是 JSON)。我们也可以对响应体进行更多的控制,比如指定响应状态码、添加自定义头部等。例如:

from fastapi import FastAPI, status, Response

app = FastAPI()

@app.get("/", status_code = status.HTTP_200_OK)
def read_root(response: Response):
    response.headers["Custom-Header"] = "Value"
    return {"Hello": "World"}

在这个例子中,通过 status_code 参数指定了响应状态码为 200 OK,并通过 response 对象添加了一个自定义头部 Custom - Header

8. 路由与模块化

8.1 路由的概念

路由是将不同的 URL 路径映射到相应的处理函数的过程。在 FastAPI 中,通过使用 app.getapp.post 等装饰器来定义路由。例如,前面的示例中:

@app.get("/items/")
def read_items():
    return [{"name": "Item 1"}, {"name": "Item 2"}]

这里定义了一个 GET 请求的路由,将 /items/ 路径映射到 read_items 函数。

8.2 模块化路由

随着项目的增长,将所有的路由都定义在一个文件中会使代码变得难以维护。FastAPI 支持模块化路由,通过 APIRouter 来实现。例如,创建一个 items.py 文件:

from fastapi import APIRouter

router = APIRouter()

@router.get("/items/")
def read_items():
    return [{"name": "Item 1"}, {"name": "Item 2"}]

@router.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id}

然后在主应用文件(比如 main.py)中引入这个路由器:

from fastapi import FastAPI
from items import router as items_router

app = FastAPI()
app.include_router(items_router)

这样,items.py 中的路由就被集成到了主应用中。模块化路由使得代码结构更加清晰,不同模块的路由可以独立开发和维护。

9. 中间件

9.1 什么是中间件

中间件是在请求到达路由处理函数之前和响应离开路由处理函数之后执行的代码。它可以用于多种目的,比如日志记录、身份验证、性能监测等。

9.2 在 FastAPI 中使用中间件

FastAPI 提供了简单的方式来添加中间件。例如,下面是一个简单的日志记录中间件示例:

import logging
from fastapi import FastAPI, Request

app = FastAPI()

logging.basicConfig(level = logging.INFO)

@app.middleware("http")
async def log_requests(request: Request, call_next):
    logging.info(f"Received request: {request.method} {request.url}")
    response = await call_next(request)
    logging.info(f"Returned response: {response.status_code}")
    return response

在这个例子中,定义了一个 log_requests 中间件函数。它接收 requestcall_next 作为参数,request 是当前的请求对象,call_next 是一个用于调用下一个中间件或路由处理函数的函数。在处理请求之前,记录请求的方法和 URL,在处理完请求后,记录响应的状态码。

10. 性能优化

10.1 异步编程

FastAPI 基于 ASGI,支持异步编程。通过使用 asyncawait 关键字,可以将路由处理函数定义为异步函数,从而提高应用的性能,尤其是在处理 I/O 密集型任务时。例如:

import asyncio
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    await asyncio.sleep(1)  # 模拟一个 I/O 操作
    return {"Hello": "World"}

在这个例子中,read_root 函数是异步的,await asyncio.sleep(1) 模拟了一个 I/O 操作(这里是等待 1 秒)。如果使用同步函数,在等待这 1 秒的过程中,整个应用会阻塞,无法处理其他请求。而使用异步函数,应用可以在等待的同时处理其他请求,提高了并发处理能力。

10.2 缓存

缓存可以显著提高 API 的性能,减少重复计算和数据库查询。可以使用第三方库(如 cachetools)来实现缓存。例如:

from fastapi import FastAPI
from cachetools import cached, TTLCache

app = FastAPI()
cache = TTLCache(maxsize = 100, ttl = 60)  # 缓存最多 100 个条目,生存时间 60 秒

@cached(cache)
@app.get("/expensive - operation/")
def expensive_operation():
    # 模拟一个耗时操作
    result = sum(range(1000000))
    return {"result": result}

在这个例子中,expensive_operation 函数被 cached 装饰器包装,使用了 TTLCache 缓存。如果在 60 秒内再次请求 /expensive - operation/,会直接从缓存中返回结果,而不会重新执行耗时的计算。

10.3 数据库连接池

在与数据库交互时,频繁地创建和销毁数据库连接会消耗大量资源。使用数据库连接池可以复用连接,提高性能。例如,对于 SQLite 数据库,可以使用 aiosqlite 库结合连接池:

import aiosqlite
from fastapi import FastAPI
from aiomysql.sa import create_engine

app = FastAPI()

# 创建数据库连接池
engine = create_engine(user = 'user', password = 'password', host = '127.0.0.1', port = 3306, db = 'test')

@app.on_event("startup")
async def startup():
    # 这里可以进行一些初始化操作,比如创建表
    async with engine.acquire() as conn:
        await conn.execute('CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, name VARCHAR(255))')

@app.on_event("shutdown")
async def shutdown():
    engine.dispose()

@app.get("/users/")
async def get_users():
    async with engine.acquire() as conn:
        result = await conn.execute('SELECT * FROM users')
        users = await result.fetchall()
        return [{"id": user[0], "name": user[1]} for user in users]

在这个示例中,使用 aiomysql.sa 创建了一个数据库连接池 engine。在应用启动时,通过 @app.on_event("startup") 装饰的函数创建了一个 users 表。在应用关闭时,通过 @app.on_event("shutdown") 装饰的函数释放连接池资源。在 get_users 函数中,从连接池中获取连接并执行数据库查询,提高了数据库操作的效率。

通过以上对 FastAPI 的详细介绍,包括其基本使用、数据处理、性能优化等方面,相信你已经掌握了使用 Python 的 FastAPI 构建高性能 RESTful API 的方法。在实际项目中,可以根据具体需求进一步扩展和优化,构建出功能强大、性能卓越的 API 服务。