ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Fastapi(1)

Fastapi(1)

数据校验

借助 Pydantic,使用标准的 Python 类型来声明请求体。

from fastapi import FastAPI from pydantic import BaseModel ​ app = FastAPI() ​ ​ class Item(BaseModel): name: str price: float is_offer: bool | None = None ​ ​ @app.get("/") def read_root(): return {"Hello": "World"} ​ ​ @app.get("/items/{item_id}") def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ​ ​ @app.put("/items/{item_id}") def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id}

Python语法特性

类型提示

Python 支持可选的“类型提示”(也叫“类型注解”)。

无类型注解
def get_full_name(first_name, last_name): full_name = first_name.title() + " " + last_name.title() return full_name ​ ​ print(get_full_name("john", "doe"))

函数功能:

  • 接收first_name和last_name。

  • 通过title()将每个参数的第一个字母转换为大写。

  • 用一个空格将它们拼接起来。

引入类型注解

如果需要调用“把首字母变大写的方法”。是upper?是uppercase?first_uppercase?还是capitalize?IDE 完全没有提示,因为IDE也不知道这个参数类型,所以不知道能调用哪些方法。

为了解决这个问题,引入类型注解;

def get_full_name(first_name: str, last_name: str): full_name = first_name.title() + " " + last_name.title() return full_name ​ ​ print(get_full_name("john", "doe"))

因为编辑器知道变量的类型,你不仅能得到补全,还能获得错误检查:

比如传参的时候传入错误类型参数,会触发检查

简单类型注解
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes): return item_a, item_b, item_c, item_d, item_e

注意:Python3 ❌ 没有 long 类型,Python3 的int就是任意精度的整数; 可以直接接收数据库 BIGINT 类型数据

typing模块

在一些额外的用例中,你可能需要从标准库的typing模块导入内容。比如当你想声明“任意类型”时,可以使用typing中的Any:

from typing import Any ​ ​ def some_function(data: Any): print(data)
泛型类型

有些类型可以在方括号中接收“类型参数”(type parameters),用于声明其内部值的类型。比如“字符串列表”可以写为list[str]。

列表
def process_items(items: list[str]): for item in items: print(item)
元组&集合
def process_items(items_t: tuple[int, int, str], items_s: set[bytes]): return items_t, items_s
字典
def process_items(prices: dict[str, float]): for item_name, item_price in prices.items(): print(item_name) print(item_price)
Union

可以声明一个变量可以是若干种类型中的任意一种,比如既可以是int** 也可以是str。定义时使用竖线(|)把两种类型分开。这称为“联合类型”(union**),因为变量可以是这两类类型集合的并集中的任意一个。

def process_item(item: int | str): print(item)
None
def say_hi(name: str | None = None): if name is not None: print(f"Hey {name}!") else: print("Hello World")
类型作为类型
class Person: def __init__(self, name: str): self.name = name ​ ​ def get_person_name(one_person: Person): return one_person.name
Pydantic 模型

Pydantic 是一个用于执行数据校验的 Python 库。

FastAPI完全建立在 Pydantic 之上。

你将数据的“结构”声明为带有属性的类。每个属性都有一个类型。然后你用一些值创建这个类的实例,它会校验这些值,并在需要时把它们转换为合适的类型,返回一个包含所有数据的对象。

你还能对这个结果对象获得完整的编辑器支持。

from datetime import datetime ​ from pydantic import BaseModel ​ ​ class User(BaseModel): id: int name: str = "John Doe" signup_ts: datetime | None = None friends: list[int] = [] ​ ​ external_data = { "id": "123", "signup_ts": "2017-06-01 12:22", "friends": [1, "2", b"3"], } user = User(**external_data) print(user) # > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3] print(user.id) # > 123

带元数据注解的类型提示

Python 还提供了一个特性,可以使用Annotated在这些类型提示中放入额外的元数据。

你可以从typing导入Annotated。

from typing import Annotated ​ ​ def say_hello(name: Annotated[str, "this is just metadata"]) -> str: return f"Hello {name}"
  1. Python 本身不会对这个Annotated做任何处理。对于编辑器和其他工具,类型仍然是str。

  2. 但你可以在Annotated中为FastAPI提供额外的元数据,来描述你希望应用如何行为。

  3. 重要的是要记住:

  4. 传给Annotated的第一个类型参数才是实际类型。其余的只是给其他工具用的元数据。

现在你只需要知道Annotated** 的存在,并且它是标准 Python。😎**

async/await

如果你正在使用第三方库,它们会告诉你使用await关键字来调用它们,就像这样:

# 阻塞等待这个函数操作完; 异步函数必须等待执行完,才能在下面拿到结果 results = await some_library()

然后,通过async def声明你的路径操作函数:

@app.get('/') async def read_results(): results = await some_library() return results

如果你正在使用一个第三方库和某些组件(比如:数据库、API、文件系统...)进行通信,第三方库又不支持使用await(目前大多数数据库三方库都是这样),这种情况你可以像平常那样使用def声明一个路径操作函数,就像这样:

@app.get('/') def results(): results = some_library() return results

Python 的现代版本支持通过一种叫"协程"——使用async和await语法的东西来写”异步代码“。

Starlette (和FastAPI) 是基于 AnyIO 实现的,这使得它们可以兼容 Python 的标准库 asyncio 和 Trio。

FastAPI 中,推荐请求处理方法全部使用async def来声明

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进