只看代码,你完全不知道应该传入什么类型的参数,来获得符合预期的结果(这里的 add 只是非常简单的例子,实际工程中可能函数实现非常复杂,只有传入了正确的类型才能获得正确的结果)。如果我们加上了 Type Hints:
def add(a: int, b: int):
return a + b
那么只看函数签名,我们就能知道传入的参数类型。
1.2 帮助代码补全或函数跳转
我们在 IDE 里写代码时,经常会使用 Tab 来补全类方法,而这个功能,也是借助 Type Hints 实现的。假设我们想实现将两个 np.ndarray 变量相加的函数:
def np_add(a, b):
c = a + b
return c.sum()
这个时候我们会发现,c.sum() 这个函数是无法自动补全/跳转的,因为我们无法推导出 c 的类型,自然也无法补全 c 的方法。上图为 vscode 展示的效果(真的是 vscode,JetBrain 主题 (●'◡'●)),vscode 插件推导 sum 的类型为 Any,自然没法补全、跳转代码。如果我们补上 Type Hints:
def np_add(a: np.ndarray, b: np.ndarray):
c = a + b
return c.sum()
此时 sum 方法既能够补全,也能够跳转。
vscode 就能解析 ndarray.sum() 的函数签名了。
1.3 在 vscode 中自动生成 docstring
1.4 增加代码鲁棒性
Type Hints 仅仅是类型提示,对变量类型没有强制性要求。换句话说,我们给函数传入和 Type Hints 不一致的变量类型,程序也不会因此而报错。为了让 Python 代码能够像 C/C++ 一样对类型做静态检查(类型不符合就报错),可以使用 mypy + Type Hints 来检查我们的代码。
import numpy as np
def np_add(a: np.ndarray, b: np.ndarray):
return a + b
np_add(1, 2)
上述代码调用 np_add 时,传入了类型不符的变量,使用 mypy 对其进行类型检查:
mypy learn_type_hint.py
learn_type_hint.py:8: error: Argument 1 to "np_add" has incompatible type "int"; expected "ndarray[Any, Any]"
learn_type_hint.py:8: error: Argument 2 to "np_add" has incompatible type "int"; expected "ndarray[Any, Any]"
from typing import Generator, Iterator
# generator 接受一个迭代器参数,函数会返回一个生成器
def generator(iterator: Iterator[int]) -> Generator[int, None, None]:
for element in iterator:
yield element
2.3 Type Hints 别名
有些变量类型的 Type Hints 过于复杂,直接在函数中声明会影响接口的可读性,因此可以重命名特定的变量类型:
from typing import Callable
def foo(a: int, b: int) -> int:
return a + b
# 不声明参数和返回值类型
def register_callback2(func: Callable)
pass
# 声明入参类型和返回类型。入参为 (int, int),返回值类型为 int
def register_callback1(func: Callable[[int, int], int]):
pass
2.5 Type Hints 对应的 docstring
参数有非 None 类型的默认值。直接在对应的 args 末尾加 Defaults to xxx 即可:
def hello(name: str = 'heihei') -> None:
"""Say hello to someone.
Args:
name (str): name of people. Defaults to "heihei".
"""
参数有默认值,默认值为 None。此时()内写(str, optional),且无需追加 Defaults to xxx:
def hello(name: str = None) -> None:
"""Say hello to someone.
Args:
name (str, optional): name of people.
"""
print(f'hello {name} ~')
参数可能是多种类型。可以写作 (str or List[str])或者 (str | List[str]):
def hello(name: Uninon[List[str], str]):
"""Say hello to someone.
Args:
name (str or List[str]): name of people.
"""
print(f'hello {name} ~')
参数可能是多种类型,且默认值为 None。可以写作 (str or List[str], optional):
def hello(name: Uninon[List[str], str] = None) - None:
"""Say hello to someone.
Args:
name (str or List[str], optional): name of people.
"""
print(f'hello {name} ~')
以上四种情况覆盖了大部分的 args 类型。给代码添加了 Type Hints 后,我们就可以用 mypy 对其进行更加准确的静态检查了。接下来,我们和大家一起来认识一下 mypy。
import numpy as np
array = np.array([1])
array.unknown()
mypy 默认 array 的类型为 Any,可以访问其任意属性。
3.1.3 函数参数的类型推导
函数入参即使有默认值,mypy 也不会推断其类型,默认类型为 Any:
class MypyDemo:
def __init__(self, parent='parent'):
self.get_value(parent)
# 尽管 parent 的默认类型和 i 的变量类型不匹配,仍然能够通过静态检查
def get_value(self, value: int):
return value
如果我们给 parent 加上 Type Hints,mypy 就会报错:
class MypyDemo:
def __init__(self, parent: str = 'parent'):
self.get_value(parent)
def get_value(self, i: int):
return i
# error: Argument 1 to "get_value" of "MypyDemo" has incompatible type "str"; expected "int"
3.2 常见问题
3.2.1 变量类型改变引起的报错
示例如下:
def parse_data(data: list):
data = torch.stack(data)
return data
这段代码是无法通过 mypy 检查的。入参 data 有 Type Hints,类型为 list,而 mypy 不允许复写变量类型。因此最好的解决方案是重命名变量:
def my_func(condition) -> dict:
result = {'success': False}
if condition:
result['success'] = True
return result
else:
result['message'] = 'error message'
return result
# error: Incompatible types in assignment (expression has type "str", target has type "bool")