如何使用Mypy的不可达代码检测?

166 阅读2分钟

我最近发现Mypy有一个辅助功能,即不可达的代码检测器。 这个功能是突出bug可能隐藏的地方的好方法,因为不可能正常运行的代码路径会显示一个逻辑错误。

我们可以通过将warn_unreachable 选项设置为true 来激活这个功能。与名称相反,该选项使Mypy对每个不可达的语句或子句记录一个错误。 我们可以像这样在setup.cfg 中设置该选项。

[mypy]
warn_unreachable = true

我们也可以在命令行中传递--warn-unreachable

Mypy可以发现很多种类的不可达代码。 例如,以这个有两个返回语句的函数为例。

def double_return() -> int:
    return 1
    return 2

当我们在这个文件上运行Mypy时,它突出显示第3行是不可达的。

$ mypy --warn-unreachable example.py
example.py:3: error: Statement is unreachable
Found 1 error in 1 file (checked 1 source file)

修复需要我们进行调查。两行return ,可能是由两个分支的错误合并引起的。我们需要弄清楚哪个返回语句是正确的,或者确实是两个都正确。

Mypy的可达性检测是细粒度的,可以突出一行中的一个子句。 例如,以这段代码为例。

def bad_check(x: int, y: int) -> None:
    if isinstance(x, int) or x < y:
        ...

if 中的第一个isinstance() 子句总是True ,所以x < y 子句是不可达的。Mypy将其突出显示为如此。

$ mypy --warn-unreachable example.py
example.py:2: error: Right operand of 'or' is never evaluated
Found 1 error in 1 file (checked 1 source file)

这种不可到达的子句可能是通过重构产生的--也许x 的类型已经从int | None 改为int ,不再需要isinstance() 的检查。

Mypy可以检测到的另一种情况是,当我们检查变量的提示说它可能不是一个类型时。 例如。

def not_noneable(x: int) -> None:
    if x is None:
        print("x is None")

Mypy告诉我们这个if 子句是不可达的。

$ mypy --warn-unreachable example.py
example.py:3: error: Statement is unreachable
Found 1 error in 1 file (checked 1 source file)

这就需要再次调查。要么是变量在其类型提示中缺少None 的选项,要么是这个if 子句可以被删除。

Mypy的不可达代码检测并不完美。 检查是基于类型的,而不是基于值的,所以它们不能检测到对人眼看到的值的重复检查。 比如说。

def double_check(x: int) -> None:
    if x == 0:
        print("x is zero")
    elif x == 0:
        print("well x is zero")

elif 不可能是真的,因为值0 已经被处理过了,但Mypy并没有突出这一点。

所有不可到达的代码最好的防御仍然是100%的代码覆盖率(是的,认真的100%!)。 但是Mypy的可到达性检测可以在进行更昂贵的测试之前,快速检查你的代码是否有潜在的错误。