mypy无法检测到条件语句

4
一个问题的工作示例(Python 3.8):
from typing import Union

class TestA:
    def test(self) -> str:
        return "testa"


class TestB:
    def test(self) -> str:
        return "testb"


tests = Union[TestA, TestB]
more = Union[TestA, TestB, str]


def test_pass(p: tests) -> str:
    return p.test() # No error


def test_fail(p: more) -> str:
    if type(p) in [TestA, TestB]:
        return p.test() # Item "str" of "Union[TestA, TestB, str]" has no attribute "test"
    else:
        return ""

print(test_fail(TestA()))  # "testa"
print(test_fail(TestB()))  # "testb"
print(test_fail("str"))  # "str"

在我的代码库中有几个地方出现了这个确切的情况,我不得不在这些行上禁用类型检查,这让我感到很烦恼。这是mypy应该忽略的问题,还是我错了?返回的错误似乎不合理,因为str永远无法到达条件语句的那一部分。

4个回答

2

哎呀,在联合类型很大的情况下(我的情况下有15种类型),这就变得棘手了。我尝试通过循环遍历类型并使用 is 进行一些诡计,但是 mypy 没有接受。它似乎正在寻找 type(p) is Type 的确切结构。 - Joshua Gilman
寻找每一个可能意味着“这个东西是X类型”的代码片段通常是不可能的(参见Rice定理)。因此,mypy采取保守立场,只接受isisinstance检查。请注意,例如,它也会理解type(p) is not str,因此,如果您只想从联合中排除一种情况,可以这样做。否则,如果您有一个大型联合并想要排除一半的情况并留下另一半,那么我建议在那时您需要考虑代码质量问题。 - Silvio Mayolo

2

mypy无法使用类似type(p) in [TestA, TestB]表达式的运行时值来确定p不是str。您可以使用typing.overload来提供两个不同的类型签名进行类型检查:一个用于tests,另一个用于str

from typing import overload


@overload
def test_fail(p: tests) -> str:
    ...

@overload
def test_fail(p: str) -> str:
    ...

def test_fail(p):
    if type(p) in [TestA, TestB]:
        return p.test()
    else:
        return ""

我不确定 mypy 如何使用这些注释来识别 tests 中的 type(p) in [TestA, TestB] 情况和 else 中的 str。在使用 mypy --strict 时似乎也不起作用,因为它会抱怨 test_fail 没有被注释类型。实际实现缺乏类型提示是故意且必要的。

或者,您可以简单地使用 typing.cast 来告诉 mypy 您知道 p 的更具体类型:

from typing import cast


def test_fail(p: more) -> str:
    if type(p) in [TestA, TestB]:
        return cast(tests, p).test() # Item "str" of "Union[TestA, TestB, str]" has no attribute "test"
    else:
        return ""

1

如果您的目标是排除单个情况,mypy也可以识别is not。但它不能识别更复杂的表达式,如in

if type(p) is not str:
    ...

0

你可以像这样使用TypeGuard:

tests = Union[TestA, TestB]

def is_tests(x) -> TypeGuard[tests]:
    return type(x) in (TestA, TestB)

然后:

if is_tests(p):
    return p.test()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接