python mypy类型检查_Python 类型检查指南

160 阅读2分钟

Mypy不允许变量改变类型,一开始我觉得有点震惊,但适应之后,我发现我的代码更有可读性。

例如,以这个我试图用DB Buddy编写的Django视图的片段为例。

from django.http import HttpRequest, HttpResponse


def avatar(request: HttpRequest) -> HttpResponse:
    size: str = request.GET.get("size", "")
    try:
        size = int(size)
    except ValueError:
        size = 40
    ...
    return HttpResponse(...)

这段代码获取了size 查询参数,这是一个字符串,并试图将其解析为int ,或者退回到默认值40 。Python可以很好地运行这段代码,但如果我们用Mypy检查它,我们会看到一些错误。

$ mypy example.py
example.py:7: error: Incompatible types in assignment (expression has type "int", variable has type "str")
example.py:9: error: Incompatible types in assignment (expression has type "int", variable has type "str")
Found 2 errors in 1 file (checked 1 source file)

我们可能会尝试通过明确声明size 正在改变类型来解决这些错误。

size: str = request.GET.get("size", "")
try:
    size: int = int(size)
except ValueError:
    size = 40

...但是Mypy不允许重新定义。

$ mypy example.py
example.py:7: error: Name 'size' already defined on line 5
example.py:9: error: Incompatible types in assignment (expression has type "int", variable has type "str")
Found 2 errors in 1 file (checked 1 source file)

我们也可以尝试用它所取的类型的联合体strint 来声明size

size: str | int = request.GET.get("size", "")

这在某些情况下是可行的,但不是普遍的。Mypy可以使用类型缩小来计算出size 必须是一个int ,但这并不总是可能的。在其他情况下,我们将不得不使用类型缩小或cast() 来告诉Mypy我们知道解析后的类型是int ,以允许int-only 操作。这种函数调用是我们代码中不必要的。

最好的解决办法是使用两个变量,第一个是str ,第二个是int

from django.http import HttpRequest, HttpResponse


def avatar(request: HttpRequest) -> HttpResponse:
    size_param: str = request.GET.get("size", "")
    try:
        size = int(size_param)
    except ValueError:
        size = 40
    ...
    return HttpResponse(...)

这使Mypy通过。

$ mypy example.py
Success: no issues found in 1 source file

这看起来似乎多了一些工作,但它澄清了代码,对Mypy和人类读者来说都是如此。当我们在函数中看到size 这个名字时,我们从第一次提到它时就知道它是一个int ,而不需要在函数中遵循一个 "类型生命周期"。

(注意:有一个配置值叫做 allow_redefinition的配置值,允许在某些情况下重新定义类型。 但我不建议打开它,反正它对这里的例子没有帮助)。