mypy方法的后置条件

2024-05-18 14:22:12 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个用数据填充的可变对象。一旦出现所有必需的数据,就可以“提交”对象。尝试提交不完整的对象会引发异常。下面是一个玩具示例,其中一个对象的content最初是None,在提交之前必须填充字符串

# inline.py
from typing import Optional

class IncompleteFoo(Exception):
    pass

class Foo:
    def __init__(self) -> None:
        self.content = None #type: Optional[str]
    def commit(self) -> str:
        if self.content is None:
            raise IncompleteFoo
        return self.content

这段代码结构不好:应该有一个单独的方法来检查完整性。(在我的实际代码中,该方法将在多个位置被调用,因为有几种不同的“提交”方式。)

# check.py:14: error: Incompatible return value type (got "Optional[str]", expected "str")
from typing import Optional

class IncompleteFoo(Exception):
    pass

class Foo:
    def __init__(self) -> None:
        self.content = None #type: Optional[str]
    def check_completeness(self) -> None:
        if self.content is None:
            raise IncompleteFoo
    def commit(self) -> str:
        self.check_completeness()
        return self.content

我使用MyPy0.780检查类型。可以理解,它抱怨上述代码:

check.py:15: error: Incompatible return value type (got "Optional[str]", expected "str")

这是公平的:在第一个“内联”版本中,mypy足够聪明,知道self.content具有类型str,因为它具有类型Optional[str],并且只有当self.content is None为false时,才能访问代码的这一部分。在使用separatecheck_completeness方法的版本中,mypy不会推断该方法的后置条件是self.content不是None

我如何让mypy知道check_completeness的后置条件是self.content is not Noneself.content : str为了保留完整性检查的封装(在我的实际代码中它要大得多),我不想在commit内重复该条件。我更愿意保持上面第二个版本的commit不变。我可以满足于重复:

# assert.py
from typing import Optional

class IncompleteFoo(Exception):
    pass

class Foo:
    def __init__(self) -> None:
        self.content = None #type: Optional[str]
    def check_completeness(self) -> None:
        if self.content is None:
            raise IncompleteFoo
    def is_complete(self) -> bool:
        return self.content is not None
    def commit(self) -> str:
        self.check_completeness()
        assert self.is_complete()
        return self.content

但这并没有帮助:mypy不会扩展方法调用来推导assert调用的后置条件


Tags: 代码selfnonereturnisdefchecktype
2条回答

您必须使用typing.cast来告诉mypy是的,这个可能是None的值实际上不会是None。如果您不想修改commit,这就有点棘手了:您需要第二个变量

from typing import Optional, cast

class IncompleteFoo(Exception):
    pass

class Foo:
    def __init__(self) -> None:
        self._content: Optional[str] = None

    def check_completeness(self) -> None:
        if self._content is None:
            raise IncompleteFoo
        self.content: str = cast(str, self._content)

    def commit(self) -> str:
        self.check_completeness()
        return self.content

如果调整commit没有问题,可以只使用一个变量,在返回值时只需调用cast

from typing import Optional, cast

class IncompleteFoo(Exception):
    pass

class Foo:
    def __init__(self) -> None:
        self.content: Optional[str] = None

    def check_completeness(self) -> None:
        if self.content is None:
            raise IncompleteFoo

    def commit(self) -> str:
        self.check_completeness()
        return cast(str, self.content)

我知道答案晚了,但我只是偶然发现了这个问题。这个怎么样

from typing import Optional

class IncompleteFoo(Exception):
    pass

class Foo:
    def __init__(self) -> None:
        self.content = None #type: Optional[str]

    def check_completeness(self) -> str:
        content = self.content
        if content is None:
            raise IncompleteFoo
        return content

    def commit(self) -> str:
        return self.check_completeness()

MyPy似乎对此很满意。以下内容似乎也是完全可以接受的:

from typing import Optional

class IncompleteFoo(Exception):
    pass

class Foo:
    def __init__(self) -> None:
        self.content = None #type: Optional[str]

    def check_completeness(self) -> str:
        content = self.content
        if content is None:
            raise IncompleteFoo
        return content

    def commit(self) -> str:
        self.content = self.check_completeness()
        # Some more arbitrary code could go here.
        return self.content
    

相关问题 更多 >

    热门问题