Python 类型模块--有效使用类型检查器

自 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中写静态键入代码的方法,这是一个动态键入语言!


参考


Published At
Categories with 技术
Tagged with
comments powered by Disqus