微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 mypy 的方法的后置条件

如何解决使用 mypy 的方法的后置条件

我有一个用数据填充的可变对象。一旦所有必需的数据都存在,就可以“提交”对象。尝试提交不完整的对象会引发异常。下面是一个玩具示例,其中一个对象的 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

我使用 mypy 0.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 时才能访问代码。在具有单独 check_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 调用的后置条件。

解决方法

您必须使用 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
    

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。