likes
comments
collection
share

typing 类型解注 全网最强攻略,妈妈再也不用担心我拼错单词辣!

作者站长头像
站长
· 阅读数 24

typing

Python的typing包是从Python 3.5版本引入的标准库,它提供了类型提示和类型注解的功能,用于对代码进行静态类型检查和类型推断。typing模块中定义了多种类型和泛型,以帮助开发者代码的可读性、可维护性和可靠性。

省流:增强编辑器的代码检查和代码提示功能

本文仅介绍 typing 模块中相对常用的东西,当你看到这篇文章,可能typing已经更新出了新东西,建议阅读本篇文章之后,看看 Python 官方文档 作为补充。

类型解注,需要IDE支持才能发挥作用,实测 Pycharm 的支持程度比 VsCode 要好。

效果

最直观的效果,就是告诉编辑器这个变量是属于什么类型的,这样他就会弹出类型相应的代码提示了。

这样可以避免很多因为拼写错误导致的error。

before:

typing 类型解注 全网最强攻略,妈妈再也不用担心我拼错单词辣!

after:

typing 类型解注 全网最强攻略,妈妈再也不用担心我拼错单词辣!

注解(术语解释)

解注 一词在Python官方文档中出现,意思就是对 Python 的对象类型进行标注,是较为官方和正统的说法

基本使用

对于普通开发者(只会用轮子的人),掌握基本用法足矣。

typgin模块在3.7版本中已经较好支持,但是一些更高级的功能需要在3.9以上实现,版本越高,对typing的支持程度更好。

目前基本每一个大版本都有对typing支持的更新。

本文写于 Python 3.11 版本,以下所有写法对于低版本的兼容性请自行检查。

对变量进行注解

在注解的同时赋值变量

# 如果被复制的变量是已知类型的,那么可以省略
var_name: int = 1  # 可以省略注解,编辑器会自动识别 var_name 为 int
var_name: float = other_unknown_type_var

也可以只解注类型,不给变量赋值

但是需要注意,如果未给变量赋值,就引用,将会抛出错误

# 常见用法
session: AsyncSession
with sessionmaker as session:
    ...

# 常见用法
name: str
idx: int
for idx, name in items:
    ...

# 错误用法:只解注,但是还未赋值就使用
var_name: int
print(var_name)
# >>> NameError: name 'var_name' is not defined

Optional 和 Union

Optional 通常用于解注某个对象是可选的,而 Union 在官方文档中的解释为联合类型,意思就是允许接受多个类型。

from typing import Union, Optional

param: Optional[int]
# 可选的int类型

param: Union[int, float]
# 可以为 int 也可以为 float

param: Optional[Union[int, float]]
# 可选的,可以为int或float

Option[Union[int, float]] ~= Union[int, float, None]
# 其实这两者在效果上是等价的

在 Python >= 3.11 中,允许使用 或运算符( | ) 代替 Union

param: int | float | None
# 效果等价于
param: Optional[Union[int, float]]
# 效果等价于
param: Union[int, float, None]

不过,在今天(2023年) 3.11 版本还是有点太新了,很多 Linux 发行版的包管理依旧还在使用 Python 3.8,建议使用比较兼容的旧版写法

函数中的注解

在函数中,可以给函数的参数返回值做解注.

OptionalUnion 的用处就在这里体现

from typing import Union, Optional

def some_func(arg1: int, arg2: Union[float], arg3: Optional[str] = None) -> int:
    ...

对于函数 参数 的解注,与上文对变量进行解注的方法一致。

对于函数 返回值 的解注,需要在括号之后,冒号之前,使用 -> type 进行解注。当然的,Union, Optional 对于返回值的解注也同样适用。

特殊的类型

你可能注意到,我们直接使用python的内置关键字 int, float, str,对变量类型进行解注,那么对于其他类型,例如 listdict 是否同样如此呢?

是,但不完全是

标注基本类型(包括:int, float, str, bool, bytes, None)是可以的。但是,如果想要原生解注其他高级类型(list, dict, tuple, ...),需要高版本Python的支持。(>= 3.9)

对于低版本的 Python,需要在 typing 模块中导入对应的model。

List

from typing import List

l: List[int] = []  # 旧版本
l: list[int] = []  # >= cpython 3.9
# 此处标注了一个内容只有int类型的列表
# 区别在于,高版本python可以直接使用 list 关键字进行解注。
# 而较低版本的 python 需要从 typing 模块中引入 List 模型进行解注。

# 解注一个可以同时拥有 int 和 float 的 list
l: List[int, float]

Dict

from typing import Dict, Union, List
from uuid import UUID

d: Dict[str, str]
# 标注一个key类型为str,value类型也为str的字典
d: dict[str, str]
# 高版本python适用写法

d: dict[UUID, Union[str, Dict, List]]
# 标注一个key的值为UUID类型,value的值可为 str, Dict, List 类型的字典

# 总结,字典类型第一个参数为 Key 类型,第二个参数为 Value 的类型

将 Class 作为类型

python 允许将 class 作为类型,可以享受代码补全功能。

class Demo:

    # 成员需要在 class-scope 解注
    data: Dict[str, int]

    def demo_1(self):
        ...

    def demo_2(self):
        ...

typing 类型解注 全网最强攻略,妈妈再也不用担心我拼错单词辣!

配合dataclasses使用

from dataclasses import dataclass, field
from typing import Optional


@dataclass
class Person:
    name: str
    age: int
    job: Optional[str] = field(default=None)
    address: Optional[str] = field(default=None)


print(Person("John", 30))

typing 类型解注 全网最强攻略,妈妈再也不用担心我拼错单词辣!

未解析引用问题

有时候,我们可能会设计一些带有嵌套结构的数据,这在ORM中十分常见。但是由于Python的顺序解析特性,可能会报错,如图:

typing 类型解注 全网最强攻略,妈妈再也不用担心我拼错单词辣!

由于 CompanyPerson 之后才被定义,所以在 Person 中引用 Company 是不被允许的。

遇到这种情况,我们可以用 引号 将类型包裹,编辑器照样可以提示,而且代码在执行过程中不会报错。

typing 类型解注 全网最强攻略,妈妈再也不用担心我拼错单词辣!

常见的另一种情况:

class Node:
    parent: Optional["Node"] = None
    children: List["Node"] = field(default_factory=list)

循环导入问题

有时候,我们在设计数据模型的时候,可能会将不同的模型分散在不同的文件之中,但是这两个模型又会如上面一样互相嵌套引用。

这个时候,我们可以借助 typing 的 TYPE_CHECKING 常量来做检查。

# person.py
import typing

if typing.TYPE_CHECKING:
    from .company import Company

@dataclass
class Person:
    name: str
    age: int
    job: Optional[str] = field(default=None)
    address: Optional[str] = field(default=None)
    company: Optional["Company"] = field(default=None)
# company.py
import typing

if typing.TYPE_CHECKING:
    from .person import Person

@dataclass
class Company:
    name: str
    address: str
    employees: list["Person"] = field(default_factory=list)

这个例子中,PersonCompany 分布在不同的文件中,并且这两个文件相互导入引用。

如果不使用 if typing.TYPE_CHECKING: 就直接导入,那么此时就会因为循环导入产生错误。

TYPE_CHECKING 只有在编辑器获取类型提示信息时才会为 True,并且不会因为循环导入产生错误。

但是,请注意

===============================

注意!注意!注意!注意!注意!

你使用 TYPE_CHECKING 导入的任何对象,只能作为注解使用,不可以真的去使用这些对象,因为这些对象只有在编辑器检查的阶段才会被导入,并且,在使用这些类型作为解注时,必须使用 引号 包裹。否则在真正的代码业务执行时,就会抛出 NameError: xxx is not defined

@dataclass
class Company:
    name: str
    address: str
    
    # 错误:
    employees: list[Person] = field(default_factory=list)
    
    # 正确:
    employees: list["Person"] = field(default_factory=list)

===============================

类型检查

如果你想检查一个对象到底是什么类型,可以使用python的 isinstance() 函数,并且编辑器会根据你的代码自动进行类型收缩。

typing 类型解注 全网最强攻略,妈妈再也不用担心我拼错单词辣!

顺便讲一下关于 isinstance(a, b) 的原理。此处的 a 必须为由 class 派生的对象,b 为用于比较的 class, 如果 b 等于 a 所派生的 class,或者是其父类,则会返回 True

class Base:
    ...

class A(Base):
    ...

class B(Base):
    ...

data = A()


isinstance(data, A)
# A 是 dataclass,为 True
# >>> True

isinstance(data, Base)
# Base 是 dataclass 的父类,为 True
# >>> True

isinstance(data, B)
# 虽然B也继承了Base,但是 B 不是 dataclass,为 False
# >>> False
转载自:https://juejin.cn/post/7266099935179046948
评论
请登录