使用 mypy 的方法的后置条件

问题描述 投票:0回答:2

我有一个可变对象,我用数据填充该对象。一旦所有必需数据都存在,就可以“提交”对象。尝试提交不完整的对象会引发异常。这是一个玩具示例,其中一个对象的

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 时才能访问这部分代码
是假的。在具有单独
check_completeness
方法的版本中,mypy 不会推断该方法的后置条件是
self.content
不是
None

我怎样才能让mypy知道

check_completeness
的后置条件是
self.content is not None
self.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
调用的后置条件。

python python-typing mypy post-conditions
2个回答
0
投票

你必须使用

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)

0
投票

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

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
    
© www.soinside.com 2019 - 2024. All rights reserved.