自 Python 3.5 以来,Python 的 typing [模块]( / 社区 / 教程 / Python - 模块)试图提供一种提示类型的方法,以帮助静态类型检查器和灯具准确地预测错误。
由于Python在运行时必须确定对象类型,因此开发人员有时很难弄清楚代码中发生了什么。
即使是PyCharm IDE 等外部类型检查器也不会产生最好的结果;平均而言,根据 StackOverflow 的答案,只有大约 50% 的时间正确地预测错误。
Python 试图通过引入所谓的 type hinting(类型注释)来缓解这个问题,以帮助外部类型检查器识别任何错误。
这使得Python代码更易于阅读和更强大,对其他读者来说!
** 注意**:如果返回的实际对象不与提示类型相同,则会出现 ** 没有** 编译错误,这就是为什么我们使用外部类型检查器,例如 mypy)来识别任何类型错误。
推荐前提条件
要有效地使用打字
模块,建议您使用外部类型检查器/linter 来检查静态类型匹配。 Python 使用的最广泛的类型检查器之一是 mypy,所以我建议您在阅读文章的其余部分之前安装它。
我们已经涵盖了 [Python 中的类型检查]( / 社区 / 教程 / Python-type-checking)的基本知识。
我们将在本文中使用mypy
作为静态类型检查器,可以通过:
1pip3 install mypy
你可以运行mypy
到任何Python文件来检查类型是否匹配,这就像你正在编译
Python代码一样。
1mypy program.py
调试错误后,您可以使用以下方法正常运行该程序:
1python program.py
现在我们已经完成了我们的前提条件,让我们尝试使用该模块的一些功能。
标签类型 / 标签类型
在功能
我们可以注明函数以指定其返回类型和参数类型。
1def print_list(a: list) -> None:
2 print(a)
这会告知类型检查器(在我的情况下mypy
)我们有一个函数print_list()
,该函数将采取一个列表
作为一个参数并返回None
。
1def print_list(a: list) -> None:
2 print(a)
3
4print_list([1, 2, 3])
5print_list(1)
让我们先在我们的类型检查器mypy
上运行这个:
1vijay@JournalDev:~ $ mypy printlist.py
2printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
3Found 1 error in 1 file (checked 1 source file)
正如预期的那样,我们会得到一个错误;因为第 5 行的参数是int
,而不是列表
。
变量
由于Python 3.6,我们还可以注明变量类型,提到类型,但如果您希望变量类型在函数返回之前发生变化,则这不是强制性的。
1# Annotates 'radius' to be a float
2radius: float = 1.5
3
4# We can annotate a variable without assigning a value!
5sample: int
6
7# Annotates 'area' to return a float
8def area(r: float) -> float:
9 return 3.1415 * r * r
10
11print(area(radius))
12
13# Print all annotations of the function using
14# the '__annotations__' dictionary
15print('Dictionary of Annotations for area():', area.__annotations__)
MyPy的输出:
1vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py
2Success: no issues found in 1 source file
37.068375
4Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}
这是使用mypy
的建议方式,首先提供类型注释,然后使用类型检查器。
类别 阿拉斯加
键入
模块为我们提供了 Type Aliases,这通过将一个类型分配给字母来定义。
1from typing import List
2
3# Vector is a list of float values
4Vector = List[float]
5
6def scale(scalar: float, vector: Vector) -> Vector:
7 return [scalar * num for num in vector]
8
9a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
10print(a)
出发点( )
1vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py
2Success: no issues found in 1 source file
3[2.0, 4.0, 6.0]
在上面的片段中,‘Vector’是一个代名词,它代表了浮点值的列表,我们可以输入一个代名词的提示,这就是上面的程序正在做的事情。
可接受的代名单的完整列表提供(https://www.python.org/dev/peps/pep-0484/#type-aliases)。
让我们看看另一个例子,它检查了字典中的每个 key:value对,并检查它们是否匹配了 name:email格式。
1from typing import Dict
2import re
3
4# Create an alias called 'ContactDict'
5ContactDict = Dict[str, str]
6
7def check_if_valid(contacts: ContactDict) -> bool:
8 for name, email in contacts.items():
9 # Check if name and email are strings
10 if (not isinstance(name, str)) or (not isinstance(email, str)):
11 return False
12 # Check for email [email protected]
13 if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
14 return False
15 return True
16
17print(check_if_valid({'vijay': '[email protected]'}))
18print(check_if_valid({'vijay': '[email protected]', 123: '[email protected]'}))
来自 mypy 的输出*
1vijay@JournalDev:~ $ mypy validcontacts.py
2validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
3Found 1 error in 1 file (checked 1 source file)
在这里,我们在mypy
中得到一个静态编译时间错误,因为我们第二个字典上的名称
参数是一个整数(123)。
使用 NewType() 创建用户定义的数据类型
我们可以使用NewType()
函数来创建新的用户定义类型。
1from typing import NewType
2
3# Create a new user type called 'StudentID' that consists of
4# an integer
5StudentID = NewType('StudentID', int)
6sample_id = StudentID(100)
静态类型检查器将对待新类型,仿佛它是原类型的一类子类型,这有助于捕捉逻辑错误。
1from typing import NewType
2
3# Create a new user type called 'StudentID'
4StudentID = NewType('StudentID', int)
5
6def get_student_name(stud_id: StudentID) -> str:
7 return str(input(f'Enter username for ID #{stud_id}:\n'))
8
9stud_a = get_student_name(StudentID(100))
10print(stud_a)
11
12# This is incorrect!!
13stud_b = get_student_name(-1)
14print(stud_b)
出发点:来自 mypy
1vijay@JournalDev:~ $ mypy studentnames.py
2studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
3Found 1 error in 1 file (checked 1 source file)
任何类型
这是一个特殊的类型,告知静态类型检查器(在我的情况下mypy
)每个类型都与这个关键字兼容。
考虑我们的旧print_list()
函数,现在接受任何类型的参数。
1from typing import Any
2
3def print_list(a: Any) -> None:
4 print(a)
5
6print_list([1, 2, 3])
7print_list(1)
现在,当我们运行mypy
时不会有错误。
1vijay@JournalDev:~ $ mypy printlist.py && python printlist.py
2Success: no issues found in 1 source file
3[1, 2, 3]
41
没有返回类型或参数类型的所有函数将默认使用任何
。
1def foo(bar):
2 return bar
3
4# A static type checker will treat the above
5# as having the same signature as:
6def foo(bar: Any) -> Any:
7 return bar
因此,您可以使用 ** Any** 来混合静态和动态键入的代码。
结论
在本文中,我们了解了 Python ** typing ** 模块,这在类型检查的背景下非常有用,允许外部类型检查器如mypy
准确报告任何错误。
这为我们提供了在Python中写静态键入代码的方法,这是一个动态键入语言!
参考
- Python 编写模块的文档 (这包含了本模块中更多方法的广泛细节,我建议作为次要参考)
- StackOverflow 关于类型提示的问题 (这为主题提供了非常好的讨论。