urain39 / stuff

Noting here.
1 stars 0 forks source link

Python中typing和mypy的使用 #56

Open urain39 opened 4 years ago

urain39 commented 4 years ago

早在2019年年中我就已经知道了Python3.5中有类型注解的存在了,但我个人使用中只局限于用默认类型,如intstr等。而同时期如prompt-toolkit(ipython的核心)的源码中早已经使用到了更加复杂化的Iterable或者是List这样的类型注解。当时我记得我好像是测试失败了就没有再使用了……

简单的类型注解

与目前其他语言一样,Python中的类型注解是直接在变量名后加上冒号和类型,如下所示:

n: int

上面我们定义了一个变量nn的类型注解为int类型。

稍复杂的类型注解

def add(x: int, y: int) -> int:
    return x + y

上面我们定义了一个add函数,其有两个int类型参数,xy。以及我们注解了这个函数的返回值也是int类型。

在交互模式的help函数中我们也可以看到关于函数的信息:

add(x: int, y: int) -> int

较为复杂的类型注解

当我们需要限制符合类型如list中的元素时,我们就需要使用到typing库了。

typing.Tuple

from typing import Tuple
address: Tuple[str, int] = ('localhost', 80)

上面的示例中我们定义了一个address变量,并注解其由一个str和一个int构成。

我们可以使用Tuple[type1, type2, ...]表示一个由type1type2……等组成的元组。

你也可以使用Tuple[int, ...](字面值省略号)表示任意长度的int类型元组,而当你仅需要一个空元组时则可以通过Tuple[()]来定义。

同理,你还可以Tuple[Any, ...]来注解一个不受限制的tuple,理论上这与直接写成tuple是等价的。

或许你还可以参考下面的typing.TypeVar使Tuple变得更加适合你。typing.TypeVar不适合用在元组里(mypy会报错)。

typing.List
此外我们还可以用List来限制一个列表中元素的类型。

namelist: List[str] = ['Alice', 'Bob', 'Candy']

List[str]表示只接受元素全为str类型的列表。Python官网文档里的示例好像是实现支持多种类型的列表:

class typing.List(list, MutableSequence[T])¶

Generic version of list. Useful for annotating return types. To annotate arguments it is preferred to use an abstract collection type such as Sequence or Iterable.

This type may be used as follows:

T = TypeVar('T', int, float)
def vec2(x: T, y: T) -> List[T]:
    return [x, y]

def keep_positives(vector: Sequence[T]) -> List[T]:
    return [item for item in vector if item > 0]

注意:上面已经提过了,TypeVar定义的类型只适合用作注解函数,不然mypy会报错!

typing.TypeVar
文档里使用的TypeVar有点类似于TypeSctipt中的type类型。

T = TypeVar('T')  # Can be anything
A = TypeVar('A', str, bytes)  # Must be str or bytes

定义后的类型不建议使用在注解变量时,mypy会报错。

typing.Literal: 用于限制一些参数的字面值。Python官网给的例子就很好:

def validate_simple(data: Any) -> Literal[True]: # always returns True
...
MODE = Literal['r', 'rb', 'w', 'wb']
def open_helper(file: str, mode: MODE) -> str:
...
open_helper('/some/path', 'r') # Passes type check
open_helper('/other/path', 'typo') # Error in type checker

这与TypeScript中的type mode = 'r' | 'rb' | 'w' | 'wb'有点类似。

typing.Any
兼容任何类型,略。

typing.Union
有点类似于TypeVar,将多个类型联合为一个整体类型,如下所示:

from typing import Union

Number = Union[int, float]
num: Number = 9

此特性在Python3.6.2加入,可以用来代替TypeVar注解变量。

typing.NoReturn
类似于TypeScript中的never关键字,用于修饰函数永远不会正确执行(即返回)。使用方法如下:

from typing import NoReturn

def throw(e) -> NoReturn
    raise e

typing.Type
这个类型注解是最有意思的,因为它是支持协变的。何为协变?参考百度百科

简而言之,如果有个类型叫做T,那么我们扩展以后的类型ET就是Type[T]类型。如下:

from typing import Type

class MyDict(dict): pass

def new_dict(dict_class: Type[dict]) -> dict:
    return dict_class()

print(new_dict(MyDict))

你也可以理解为它是类型的类型。

其他类型注解
仅以简写格式列出来其他一些常用类型注解:

配合mypy一起使用

为什么我们学会了类型注解仍需要mypy呢?

以最开始的n: int为例,按照逻辑来,我们现在可以将任意一个int类型的值赋给n,如n = 9。但是实际上Python并不真的对类型进行检查!

若是此时将任意字符串赋给n,如n = '9',Python也不会跑出诸如TypeError一样的异常!

此外还需要注意与其他语言不同,Python中的类型注解没有初始化值!此时若是在REPL模式下操作,键入n回车后会报NameError错误:

>>> n: int
>>> n
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'n' is not defined
>>>

因此这里我们就需要使用到mypy来替我们检查了,通过命令pip install mypy即可安装。

使用方法:mypy <目录 或 文件>

Refs:

urain39 commented 4 years ago

此外还可以给类型取别名,比如:

from typing import Union

Number = Union[int, float]