带mymyy的方法的后置条件



我有一个可变对象,我填充数据。一旦所有必需的数据都存在,对象就可以"提交"了。试图提交不完整的对象会引发异常。这里有一个玩具样例,对象的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

如何让mymyy知道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()

我似乎对此很高兴。以下内容似乎也完全可以接受:

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

相关内容

  • 没有找到相关文章

最新更新