使用mypy提高签名质量¶
初步¶
PySide的Python接口文件是由几个脚本生成的。
当.pyi
文件在2017年启动时,可以进行最小的语法检查,
因为这些文件可以在Python
本身中运行。
对.pyi
文件格式的一些更改使得这变得不可能,导致PySide
的.pyi
文件多年来几乎未被检查。只有所有函数的正确解析才能通过生成器进行检查。
引入mypy
工具作为生成文件的严格错误检查器带来了许多改进,但也有一些意外。
运行mypy测试¶
mypy
测试由 Qt 公司的 CI 框架(COIN)自动运行。
当你安装了 mypy
时,测试会在构建时运行。
在调试模式下,这可能需要超过 30 秒,因此我们提供了翻译选项
--skip-mypy-test
可以在重复翻译时使用。但请注意,mypy
有一个很好的缓存,可以抑制对未更改的 .pyi
文件的分析。
mypy 错误类型¶
重复错误¶
许多函数具有多个签名,这些签名随后会在.pyi
文件中被转换为多个typing.overload
版本。由于C++
函数到Python
的映射,有时会发生类似的C++
函数变成Python
中的重复项。这很容易过滤掉,但mypy
仍然会发现仅在参数名称上有所不同的重复项。现在,这由模块layout
中的函数remove_ambiguous_signatures()
处理,该函数比较所谓的annotations
,这些注释会忽略参数名称。
阴影错误¶
一个相当微妙的错误类型是多个签名的遮蔽。这是由于.pyi
文件的顺序性质造成的:
* In ``C++``, the order of functions does not matter at all. The best fit is
automatically used.
* In Python stub files, the alternatives of multiple signatures are sequentially
checked in ``@typing.overload`` chains of functions.
This can produce shadowing when an annotation contains another.
一个例子:PySide6.QtCore.QCborSimpleType
当 int 列在前面时,会被 int 遮蔽。这是由于方法解析顺序 mro()
的原因:
* int.mro() [<class 'int'>, <class 'object'>]
* QCborSimpleType.mro() [<enum 'QCborSimpleType'>, <enum 'IntEnum'>,
<class 'int'>, <enum 'ReprEnum'>,
<enum 'Enum'>, <class 'object'>]
你可以看到mro()
对多个签名有排序效果。
枚举继承自int
,应该排在int
条目之前。
将多个签名带入无冲突顺序的整个任务是一种拓扑排序
。
我们使用参数注释的mro
长度和一些额外的启发式方法构建一个排序键。它们可以在模块layout
中的sort_by_inheritance()
调用的函数get_ordering_key()
中进行检查。
无法解决的错误¶
一些由mypy指出的错误我们无法解决。我们唯一的机会是部分或完全禁用这些错误。它们在.pyi
文件中被标记,见下文。
与Qt的矛盾¶
mypy 发现错误的地方,Qt 有不同的看法。错误类型“override”和“overload-overlap”需要被禁用,因为我们无法改变 Qt 认为正确的内容。
示例:
Error code "override" cannot be fixed because the problem
is situated in Qt itself:
Signature of "open" incompatible with supertype "QFile"
Error code "overload-overlap" also cannot be fixed because
we have no chance to modify return-types:
Overloaded function signatures 1 and 6 overlap with
incompatible return types
它们通过以下注释全局禁用:
# mypy: disable-error-code="override, overload-overlap"
其他错误如“misc”过于宽泛,不能过早禁用。 请参见下面我们如何处理它们。
与 __add__ 和 __iadd__ 的分歧¶
有一些内部规则适用于Python
,这些规则只有在mypy
将其标记为“misc”时才能被识别。有些函数是成对出现的:
__add__, __iadd__, __sub__, __isub__, __mul__, __imul__, ...
以及更多。有这条规则:
if __add__ and __iadd__ exist in a type, the signatures must be the same.
在95%的情况下,这条规则是满足的,但在少数情况下并不满足。在这些情况下,我们必须计算这些情况,如果它们不一致,我们会生成一个禁用的mypy
内联注释“# type: ignore[misc]”。你可以在模块enum_sig
的ExactEnumerator.klass
中看到这个功能。
与不一致的重载存在分歧¶
如果存在方法和静态或类方法的混合重载,mypy认为这是一个错误。在第一个版本中,我们通过抑制这种“杂项”错误来修复这种罕见的情况。但在转向正确的位置参数(PEP 570)时,这种抑制导致了无法解决的后续错误。更干净的解决方案是移除静态方法并优先使用普通方法。
查看模块 layout
中的函数 is_inconsistent_overload()
,该函数检查“self”是否始终或从不作为参数。
结论与未来¶
这项工作已将报告的mypy
错误从601个减少到零,这确实是一个改进。但还可以做得更多。尽管我们现在知道我们生成的文件在语法和语义上都非常正确,但我们仍然不知道实际类型是否真正满足mypy
的要求。
在mypy
中有一个stubtest
模块,我们或许可以用它来进行更多的测试。这些测试将检查实现和存根文件是否一致。
文献¶
- mypy error codes
我们默认启用了这些代码。
- typing — 支持类型提示
typing 模块的 Python 文档
- Typing cheat sheet
类型提示的快速概览(托管在mypy文档中)
- mypy 文档中的“类型系统参考”部分
Python 的类型系统通过 PEP 标准化,因此此参考应广泛适用于大多数 Python 类型检查器。(某些部分可能仍然特定于 mypy。)
- Static Typing with Python
由社区编写的与类型检查器无关的文档,详细介绍了类型系统特性、有用的类型相关工具和类型最佳实践。
- Python类型系统的规范
完整的规范。非常详尽。