FastAPI官档精编003 - Python类型提示

呆鸟云:发布本系列旨在推广 FastAPI 以及推进 FastAPI 中文官档翻译,目前,FastAPI 官档已完成 98% 的中文翻译,如果您对 FastAPI 有兴趣,可以为这个很赞的开源项目做些贡献,比如校译、翻译、审阅等。

开源项目的发展离不开大家的支持。当然最简单的就是在 Github 上点 Star 了。

如果您觉得 FastAPI 不错,也可以转发、转载本文,让更多人知道 Python 还有这么简单的后端支持库。

FastAPI 官档地址:https://fastapi.tiangolo.com/zh/

下面先列出几个需要 Review 的 PR,希望大家多多参与。

以下为正文。


Python 的 类型提示是声明变量类型的语法。

声明了变量类型,编辑器和开发工具就能提供更好的支持。

本章只是 Python 类型提示的快速入门,仅介绍了 FastAPI 中与类型提示相关的内容……真的很少。

FastAPI 是基于类型提示开发的,写代码的体验非常不错。

就算不使用 FastAPI,了解一下类型提示也会让您获益匪浅。

!!! note "笔记"

如果您是 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"))

这段代码输出如下内容:

John Doe

该函数执行以下操作:

  • 接收 first_namelast_name 参数
  • 使用 title() 把参数的首字母转换为大写
  • 使用空格<abbr title="按顺序把多个内容组合成一个整体。">拼接</abbr>两个参数的值
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"))

编辑示例

这是个非常简单的程序。

现在,假设您要从头编写这段程序。

在某一时刻,开始定义函数,并且准备好了参数……

此时,需要调用把首字母转换为大写的方法

等等,那个方法是什么来着?upperuppercasefirst_uppercase?还是capitalize

然后,您尝试向程序员老手的朋友——编辑器自动补全寻求帮助。

输入函数的第一个参数 first_name,输入点号(.),然后敲下 Ctrl+Space 触发代码补全。

可惜,这没有什么用:

添加类型

接下来,修改上例中的一行代码。

把下面这行代码中的函数参数从:

    first_name, last_name

改成:

    first_name: str, last_name: str

就是这样。

这就是类型提示

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"))

与声明默认值不同,例如:

    first_name="john", last_name="doe"

这两者不一样。

类型提示用的是冒号(:),不是等号(=)。

而且类型提示一般不会改变原有的运行结果。

再次创建这个函数,这次添加了类型提示。

在同一个位置,使用 Ctrl+Space 触发自动补全,就会发现:

这样,就可以滚动查看选项,找到需要的功能:

更多动机

下面是个使用类型提示的函数:

def get_name_with_age(name: str, age: int):
    name_with_age = name + " is this old: " + age
    return name_with_age

因为编辑器已经知道了变量的类型,所以不仅能对代码进行补全,还能检查代码错误:

现在,必须先修复这个问题,使用 str(age)age 转换成字符串:

def get_name_with_age(name: str, age: int):
    name_with_age = name + " is this old: " + str(age) # 看这行
    return name_with_age

声明类型

您刚刚看到的就是类型提示常见的场景 ~ 用于函数的参数。

这也是在 FastAPI 中类型提示的常用场景。

简单类型

类型提示不只使用 str,还能声明所有 Python 标准类型。

比如,以下类型:

  • int
  • float
  • bool
  • bytes
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_d, item_e

通用类型的类型参数

dictlistsettuple 等数据结构可以包含其它值,而且其内部的值也有自己的类型。

Python 的 typing 标准库可以声明这些类型及其子类型。

这个标准库专门用来支持类型提示。

列表

例如,定义由 str 组成的 list 变量。

typing 模块导入 List(注意 L 要大写):

from typing import List

def process_items(items: List[str]):
    for item in items:
        print(item)

同样用冒号(:)声明变量。

类型是 List

由于列表是包含子类型的类型,所以要把子类型放在方括号里:

from typing import List

def process_items(items: List[str]): # 看这行
    for item in items:
        print(item)

!!! tip "提示"

方括号里的内部类型叫做**类型参数**。

此时,`str` 是传递给 `List` 的类型参数。

即:变量 items 的类型是 list,并且列表里的每个元素的类型都是 str

这样,即使在处理列表里的元素时,编辑器也能提供支持:

没有类型,这种支持几乎不可能实现。

注意,变量 item 是列表 items 里的元素。

而且,编辑器仍然把它识别为 str,并提供相关支持。

元组和集合

声明 tupleset 的方法也一样:

from typing import Set, Tuple # 看这行

def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]): # 看这行
    return items_t, items_s

即:

  • 变量 items_t 是包含 3 个元素的 tuple,分别是 intintstr
  • 变量 items_sset,其中每个元素的类型都是 bytes

字典

定义 dict 要传入 2 个用逗号分隔的子类型。

第一个子类型声明 dict 的所有键。

第二个子类型声明 dict 的所有值:

from typing import Dict


def process_items(prices: Dict[str, float]): # 看这行
    for item_name, item_price in prices.items():
        print(item_name)
        print(item_price)

即:

  • 变量 pricesdict
    • dict 的键的类型是 str(元素名称)
    • dict 的值的类型是 float(元素价格)

Optional

使用 Optional 把变量声明为 str 等类型,但该类型是可选的,因此也可以是 None:

from typing import Optional

def say_hi(name: Optional[str] = None): # 看这行
    if name is not None:
        print(f"Hey {name}!")
    else:
        print("Hello World")

使用 Optional[str] 替代简单的 str 可以让编辑器检测值的类型应为 str,但也可能为 None 的错误。

通用类型

以下类型使用方括号中的类型参数:

  • List
  • Tuple
  • Set
  • Dict
  • Optional
  • 等……

这些类型就是通用类型,也叫作 Generics

类作为类型

类也可以声明为变量的类型。

假设有一个包含 name 属性的类 Person

class Person:
    def __init__(self, name: str):
        self.name = name

def get_person_name(one_person: Person):
    return one_person.name

下面,把变量的类型声明为 Person

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 typing import List, Optional

from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = "John Doe"
    signup_ts: Optional[datetime] = 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

!!! info "说明"
FastAPI 就是基于 Pydantic 开发的。

用户指南中列出了很多示例。

FastAPI 中的类型提示

FastAPI 充分利用了类型提示的优势。

FastAPI 使用类型提示声明参数可以获得:

  • 编辑器支持
  • 类型检查

……FastAPI 还使用类型声明:

  • 定义参数需求:声明对请求路径参数、查询参数、请求头、请求体、依赖项等的需求
  • 转换数据:把请求中的数据转换为需要的类型
  • 校验数据: 对于每一个请求:
    • 数据校验失败时,自动生成错误信息,并返回给客户端
  • 使用 OpenAPI 存档 API:
    • 并在 API 文档中显示

听上去有点抽象,不过不用担心。用户指南中详细介绍了上述所有内容。

最重要的是,使用 Python 标准类型,只要在一个地方声明(不用添加更多的类、装饰器等),FastAPI 就能完成很多工作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容