0

Python 类型注解 - 奥森iorson

 1 year ago
source link: https://www.cnblogs.com/iorson/p/17114352.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Python 类型注解

在Python语言发展的过程中,PEP提案发挥了巨大的作用,如PEP 3107 和 PEP 484提案,分别给我们带来了函数注解(Function Annotations)和类型提示(Type Hints)的功能。

PEP 3107:定义了函数注解的语法,允许为函数的参数和返回值添加元数据注解。

PEP 484:按照PEP 3107函数注解的语法,从Python语法层面全面支持类型提示,类型提示可以是内置类型、内置类、抽象基类、types模块中提供的类型和开发人员自定义的类。

另外 PEP 526, PEP 544, PEP 586, PEP 589, PEP 591 这些东西对 PEP 3107 和 PEP 484 进行了补充,比如添加了变量注释,字面量注释这些东西。

需要注意的是,类型提示仅有提示的作用,这里的提示是指用户阅读Python代码的时候的提示,仅在语法层面支持,对代码的运行没有任何影响,Python 解释器在运行代码的时候会忽略类型提示,也就是说,Python的类型提示仅是为了提升代码可读性,一定程度上缓解"动态语言一时爽,代码重构火葬场"的尴尬。

下面将函数注解和类型提示,统称为类型注解。

类型注解优点

1、可以使Python拥有部分静态语言的特性,利用类型注解可以实现一种类似类型声明的效果,提升代码的可读性及后续的可维护性。

2、类型注解可以让IDE(如pycharm)像静态语言那样分析我们的代码,及时给我们相应的提示,如下图对比:

image-20230212140705227

VS

image-20230212140817448

3、多多使用类型注解,不仅可以让Python拥有强类型语言的严谨,还能保持Python作为动态类型语言的灵活性。

普通变量类型注解

在声明变量时,变量的后面可以加一个冒号,后面再写上变量的类型,如 int、list 等等,以此实现类型注解。

a: int = 22
b: str = "name"
c: float = 55.5
d: bool = True
e: list = [1, 2, 3]
f: set = {1, 2, 3}
g: dict = {"name": "ming", "age": 22}
h: tuple = (1, 2, 3)
i: bytes = b'world'
j: bytearray = bytearray("world")

函数参数及返回值类型

函数参数的类型声明就是冒号+类型即可,和普通变量类型声明没区别。

函数返回值的类型声明是用箭头指向具体的类型,如果是返回值有多个,使用元组包裹即可(因为函数的多个返回值就是以元组形式返回的),需要注意的是,箭头左右两边都要留有空格。

def handler(a: int, b: int) -> int:
    return a + b


def handler2(a: int, b: int, *args: int) -> int:
    return a + b + sum(args)


def handler3(a: int, b: int, *args: int, **kwargs: int) -> (int, str):
    return a + b + sum(args) + sum(kwargs.values()), ""

typing模块

typing模块的加入不会影响程序的运行,也不会报正式的错误,pycharm支持检测基于typing注解的错误,不符合规定类型注解时会出现黄色警告,但不会影响程序运行。

容器类型 & 复合类型

列表、字典、元组等包含元素的复合类型,用简单的 list,dict,tuple 不能够明确说明内部元素的具体类型。

此外,Python本身就是动态类型的语言,如果我们强制使用某种类型,一定程度上会丧失Python作为动态语言的优势,因此 typing 模块提供了一种复合类型注解的语法,即一个参数即可以是类型A,也可以是类型B或者类型C

from typing import Dict, List, Set, Tuple, Union

# 字典
d: Dict[str, int] = {"a": 1, "b": 2}
d1: Dict[str, int or str] = {"a": 1, "b": "2"}  	# 使用or表示支持多个类型

# 列表
l: List[int] = [1, 2, 3]
l1: List[int or str] = [1, 2, "3"]

# 元组
t: Tuple[str, int] = ("a", 1)		# 代表了构成元组的第一个元素是 str 类型,第二个元素是 int 类型
t1: Tuple[str, ...] = ("a", "b", "c", "d", "e", "f", "g")		# 代表接受多个 str 类型的元素
t2: Tuple[str or int, ...] = ("a", "b", 2)		# 代表接受多个 str 或 int 类型的元素

# 集合
s: Set[int] = {1, 2, 3, 4}
s1: Set[Union[int, str, float]] = {1, "2", 3.333, 4}	# Union 同 or

TypedDict

TypedDict声明一个字典类型,该类型期望它的所有实例都有一组固定的keys,其中每个key都与对应类型的值关联。

from typing import TypedDict


class Student(TypedDict):
    name: str
    age: int
    height: float


s1: Student = {
    "name": "xiao ming",
    "age": 22,
    "height": 55.5
}

s2: Student = {
    "name": "xiao hong",
    "age": 21,
}
image-20230212162843194

可以看出,pycharm也会警告我们字典实例中缺失的key。

同时,在我们生成字典实例的时候,pycharm也会给我们key的提示。

image-20230212163042746

类型别名是通过将类型分配给别名来定义的,类型别名可用于简化复杂类型提示。

from typing import Union

Number = Union[int, float]

def process(v: Number) -> Number:
    return v

x: Number = 2
y: Number = 2.2
process(x)
process(22)		# 类型检查成功,类型别名和原始类型是等价的

NewType

使用NewType辅助类来创建不同的类型

from typing import NewType

Number = NewType("Number", int)

def process(v: Number) -> Number:
    return v

x: Number = Number(22)
process(x)
process(22)     # 类型检查异常:Expected type 'Number', got 'int' instead 
# 原因就是NewType创建的是原始类型的“子类型”

因此,类型别名 和 NewType 具体使用哪个,要视情况而定,不知道使用哪个,可以先使用类型别名。

NoReturn

当一个方法没有返回结果时,为了注解它的返回类型,我们可以将其注解为 NoReturn。

因为Python 的函数运行结束时隐式返回 None ,这和真正的无返回值是有区别的。

from typing import NoReturn

def process() -> NoReturn:
    pass

可选类型:Optional

使用 Optional[] 表示可能为 None 的值

from typing import Optional

def handler(x: int) -> Optional[int]:
    if x % 2 == 0:
        return x

可调用对象:Callable

若一个变量类型是可调用函数,则可以用 Callable[[Arg1Type, Arg2Type], ReturnType] 实现类型提示

from typing import Optional, Callable

def handler(x: int) -> Optional[int]:
    if x % 2 == 0:
        return x

def handler2(func: Callable[[int], Optional[int]]):
    pass

handler2(handler)

字面量:Literal

指示相应的变量或函数参数只接收与提供的字面量(或多个字面量之一)等效的值,可以理解为规定了某个参数或变量的所有枚举值。

from typing import Literal, NoReturn

Mode = Literal["r", "w"]


def process(mode: Mode) -> NoReturn:
    pass


process("s")
image-20230212162309699

可以看出,pycharm检查出了我们输入的值并不符合字面量规定的值,进而出现了黄色警告。

image-20230212163929671

是一种特殊的类型,每种类型都视为与Any兼容,同样,Any也与所有类型兼容。可以对Any类型的值执行任何操作或方法调用,并将其分配给任何变量。将Any类型的值分配给更精确的类型(more precise type)时,不会执行类型检查,所有没有返回类型或参数类型的函数都将隐式地默认使用Any。

使用Any,说明值是动态类型。

把所有的类型都注解为 Any 将毫无意义,因此 Any 应当尽量少使用

from typing import Any

def foo() -> Any:
    pass
# 在某些情况下,我们可能并不需要严格区分一个变量或参数到底是列表 list 类型还是元组 tuple 类型
# 可以使用一个更为泛化的类型,叫做 Sequence,其用法类似于 List
class typing.Sequence(Reversible[T_co], Collection[T_co])


# collections.abc.Iterator的泛型版本
# 注释函数参数中的迭代类型时,推荐使用的抽象集合类型
class typing.Iterable(Generic[T_co])

def print_iterable(x: Iterable):
    for i in x:
        print(i)



# collections.abc.Mapping的泛型(generic)版本
# 注释函数参数中的Key-Value类型时,推荐使用的抽象集合类型
class typing.Mapping(Sized, Collection[KT], Generic[VT_co])

泛型:TypeVar

先抛出问题:

假设有一个函数,要求它既能够处理字符串,又能够处理数字。那么你可能很自然地想到了 Union ,如下:

from typing import Union

AddValue = Union[int, str]


def add(a: AddValue, b: AddValue) -> AddValue:
    return a + b


if __name__ == "__main__":
    print(add(1, 2))        # 类型检查通过,输出 3
    print(add("1", "2"))    # 类型检查通过,输出 12
    print(add("1", 2))      # 类型检查通过,报错 TypeError: can only concatenate str (not "int") to str

在类型检查通过的情况下,我们完成并运行了这段代码,可是代码却报错了!

原因就是我们的初衷是数字和数字相加实现求和,字符串和字符串相加实现拼接,没有考虑到字符串与数字混用的问题,从而引发错误。

根据以上问题,我们可以引入泛型来解决这个问题:

from typing import TypeVar

AddT = TypeVar("AddT", int, str)


def add(a: AddT, b: AddT) -> AddT:
    return a + b


if __name__ == "__main__":
    print(add(1, 2))		# 类型检查通过,输出 3
    print(add("1", "2"))	# 类型检查通过,输出 12
    print(add("1", 2))		# 类型检查失败,pycharm告警 Expected type 'str' (matched generic type 'AddT'), got 'int' instead

"""
通过告警,我们提前发现了混用类型的问题,避免了程序运行时发生异常的可能。
"""

泛型很巧妙地对类型进行了参数化,同时又保留了函数处理不同类型时的灵活性。

1、Python 标准库 typing 类型注解标注

2、Python类型注解,你需要知道的都在这里了


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK