Python中的渐进式输入
渐进类型是由 Jeremy Siek 和 Walid Taha 在 2006 年开发的类型系统,它允许程序的一部分是动态类型,而其他部分是静态类型。这意味着,程序员可以选择他/她想要键入检查的程序的哪一部分。
渐进类型检查器是一种静态类型检查器,用于检查渐进类型程序的静态类型部分中的类型错误。类型检查器通过将变量和函数参数分配给称为Any
的特殊类型来处理逐渐类型化程序的动态类型化部分。静态类型检查器会将每种类型视为与 Any 兼容,将 Any 视为与每种类型兼容。这意味着可以对 Any 类型的值执行任何操作或方法调用并将其分配给任何变量,静态类型检查器不会抱怨。
为什么我们需要一个静态类型检查器?
- 在程序的静态类型部分中更快地发现错误
- 项目越大,调试运行时类型错误变得越困难
- 它有助于理解团队中新工程师的程序,因为在Python中很难遵循对象流
背景资料:
2014 年,Guido van Rossum 与 Jukka Lehtosalo 和 Lukasz Langa 一起为 Type Hints 提出了PEP 484提案。目标是为类型注释提供标准语法,打开Python代码以更轻松地进行静态分析。在 2006 年, PEP 3107已经引入了函数注释的语法,但由于没有明确的第三方工具将如何使用它的概念,因此故意未定义语义。
提案大纲:
- 开发人员选择是否使用名为静态类型检查器的单独程序
- 函数注释是临时的,仅由静态类型检查器使用
- 第三方库、标准库、C 扩展、所有者选择不注释的代码,为了 PY2 兼容性,注释需要时间,在这种情况下使用类型注释不可行,开发人员可以进行类的虚拟声明和函数在一个名为存根文件的单独文件中,只有静态类型检查器才能看到
- 受到 Jukka Lehtosalo 开发的静态类型检查器 Mypy 的强烈启发
为什么我们需要类型提示?
- 帮助类型检查器
- 作为附加文件
- 帮助 IDE 改进建议和代码检查
函数注释的几个基本示例:
示例 1:
# Python program to demonstrate
# function annotations
# Setting the arguments type and
# return type to int
def sum(num1: int, num2: int) -> int:
return num1 + num2
# will not throw an error
print(sum(2, 3))
# will raise a TypeError
print(sum(1, 'Geeks'))
输出:
5
Traceback (most recent call last):
File "/home/1c75a5171763b2dd0ca35c567f855c61.py", line 13, in
print(sum(1, 'Geeks'))
File "/home/1c75a5171763b2dd0ca35c567f855c61.py", line 7, in sum
return num1 + num2
TypeError: unsupported operand type(s) for +: 'int' and 'str'
示例 1 是一个简单函数,其参数和返回类型在注释中声明。这表明参数num1
和num2
的预期类型是int
,预期返回类型是int
。该参数也接受其类型是特定参数类型的子类型的表达式。
示例 2:
# Python program to demonstrate
# function annotations
# Setting the arguments type and
# return type to str
def say_hello(name: str) -> str:
return 'Hello ' + name
# will not throw an error
print(say_hello("Geeks"))
# will raise a TypeError
print(say_hello(1))
输出:
Hello Geeks
Traceback (most recent call last):
File "/home/1ff73389e9ad8a9adb854b65716e6ab6.py", line 13, in
print(say_hello(1))
File "/home/1ff73389e9ad8a9adb854b65716e6ab6.py", line 7, in say_hello
return 'Hello ' + name
TypeError: Can't convert 'int' object to str implicitly
在示例 2 中, name
参数的预期类型是str
。类似地,预期的返回类型是str
。
变量注释的几个基本示例:
PEP 484 引入了类型提示,也就是类型注释。虽然它的主要重点是函数注释,但它还引入了类型注释的概念来注释变量:
示例 1:
# Python program to demonstrate
# variable annotations
# declaring the list to be
# of int type
l = [] # type: List[int]
# declaring the variable to
# be str type
name = None # type: str
尽管存在模棱两可的情况,但变量类型由初始化程序推断。例如,在示例 1 中,如果我们不对变量l
进行注释,它是一个空列表,静态类型检查器将抛出错误。同样,对于未初始化的变量name
,需要将其分配给类型 none 以及类型注释,否则,静态类型检查器将抛出错误。
PEP 526 引入了用于注释变量类型(包括类变量和实例变量)的标准语法,而不是通过注释来表达它们:
示例 2:
# Python program to demonstrate
# variable annotations
l: List[int] = []
name: str
示例 2 与示例 1 相同,但使用 PEP 526 中引入的标准语法,而不是变量类型注释的注释样式。请注意,在此语法中,无需将变量name
指定为无类型。
静态类型检查器示例:
Mypy是Python 3 和Python 2.7 的静态类型检查器。使用Python 3函数注释语法(使用 PEP 484 表示法)或Python 2 代码的基于注释的注释语法,您将能够有效地注释您的代码并使用mypy
检查代码中的常见错误。
Mypy 需要Python 3.5 或更高版本才能运行。安装Python 3 后,使用 pip 安装mypy
:
$ python3 -m pip install mypy
安装 mypy 后,使用 mypy 工具运行它:
$ mypy program.py
此命令使 mypy 类型检查您的 program.py 文件并打印出它发现的任何错误。 Mypy 会静态地检查你的代码:这意味着它会在不运行你的代码的情况下检查错误,就像 linter 一样。
虽然您必须安装Python 3 才能运行 mypy,但 mypy 也完全能够对Python 2 代码进行类型检查:只需传入 –py2 标志即可。
$ mypy --py2 program.py
Mypy 抛出的类型错误示例:
# Python program to demonstrate
# mypy
def sum(a: int, b: int) -> int:
return a + b
sum( 1, '2') # Argument 2 to "sum" has incompatible type "str"; expected "int"
sum(1, b '2') # Argument 2 to "sum" has incompatible type "bytes"; expected "int"
生产应用中的渐进式打字:
Lukasz Langa 在 PyCascade-2018 上发表了关于生产应用程序中的渐进式打字的演讲。他给出了如下工作流程建议:
- 工作流程建议 #1:找到最关键的功能并首先开始输入。例如,首先键入广泛使用的函数和广泛导入的模块,因为这允许使用这些模块和函数的代码更有效地进行类型检查。
- 工作流程建议 #2:尽早启用文件级 linter 类型检查。例如, flake8-mypy 只呈现与当前文件和标准库相关的类型错误。
- 工作流程建议 #3:在持续集成中启用完整的程序类型检查以对抗回归。例如,使用 mypy 进行完整的代码库检查作为持续集成的一部分,以防止由于新代码导致现有代码中的类型错误。
- 工作流程建议 #4:测量函数覆盖率和生产中的 TypeError/AttributeErrorexceptions 的数量。这清楚地说明了如何对剩余的代码库进行渐进式输入。
结论:
- 只有带注释的函数会被类型检查,而未注释的函数会被静态类型检查器忽略。
- 类型检查器的干净运行并不能证明没有错误。随着您逐渐注释剩余的代码库,可能会出现新的类型错误。
- 新的注释可以发现类型检查器之前没有抛出任何错误的其他函数中的错误。