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)
我们也可以尝试用它所取的类型的联合体str 和int 来声明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的配置值,允许在某些情况下重新定义类型。 但我不建议打开它,反正它对这里的例子没有帮助)。