属性注解,用于检查分配给初始设置为无的保护值

JL *_*ret 6 python type-hinting mypy

我有一个属性,每个实例仅计算一次,并None用作保护值。这是带有属性的非常常见的模式。

注释的最佳方法是什么?

编辑/说明:

这个问题是关于如何使用mypy来验证我物业的代码。与如何重构代码无关。作为非可选的int,属性的类型就是我想要的方式。例如,假设下游代码将执行print(book.the_answer+1)

不正确的字符串分配和None完全是为了破坏按物业期望定义的合同,我希望mypy对其进行标记。

尝试#1

class Book:

    def __init__(self, title):
        self.title = title

    _the_answer = None #line 6
    # _the_answer : int = None #line 7

    def __str__(self):
        return "%s => %s" % (self.title, self.the_answer)

    @property
    def the_answer(self)->int:
        """always should be an int.  Not an Optional int"""
        if self._the_answer is None:
            if "Guide" in self.title:
                #this is OK
                self._the_answer = 42
            elif "Woolf" in self.title:
                #this is wrong, mypy should flag it.
                self._the_answer = "?, I haven't read it"  # line 21
            else:
                #mypy should also flag this
                self._the_answer = None #line 24

        return self._the_answer #line 26

print(Book("Hitchhiker's Guide"))
print(Book("Who's afraid of Virginia Woolf?"))
print(Book("War and Peace"))
Run Code Online (Sandbox Code Playgroud)

输出:

Hitchhiker's Guide => 42
Who's afraid of Virginia Woolf? => ?, I haven't read it
War and Peace => None
Run Code Online (Sandbox Code Playgroud)

Mypy的输出:

test_prop2.py:21: error: Incompatible types in assignment (expression has type "str", variable has type "Optional[int]")
test_prop2.py:26: error: Incompatible return value type (got "Optional[int]", expected "int")
Run Code Online (Sandbox Code Playgroud)

第26行实际上并没有错,这完全取决于IF中分配的内容。21和24都不正确,但是mypy仅捕获21。

注意:如果我将属性更改为return,return cast(int, self._the_answer) #line 26则至少将其保留。

如果将类型添加到保护值中,请执行以下操作_the_answer

尝试#2,也输入保护值:

class Book:

    def __init__(self, title):
        self.title = title

    #_the_answer = None
    _the_answer : int = None #line 7

    def __str__(self):
        return "%s => %s" % (self.title, self.the_answer)

    @property
    def the_answer(self)->int:
        """always should be an int.  Not an Optional int"""
        if self._the_answer is None:
            if "Guide" in self.title:
                #this is OK
                self._the_answer = 42
            elif "Woolf" in self.title:
                #this is wrong.  mypy flags it.
                self._the_answer = "?, I haven't read it"  # line 21
            else:
                #mypy should also flag this
                self._the_answer = None #line 24

        return self._the_answer #line 26

print(Book("Hitchhiker's Guide"))
print(Book("Who's afraid of Virginia Woolf?"))
print(Book("War and Peace"))
Run Code Online (Sandbox Code Playgroud)

运行输出相同,但是mypy具有不同的错误:

test_prop2.py:7: error: Incompatible types in assignment (expression has type "None", variable has type "int")
Run Code Online (Sandbox Code Playgroud)

并且它没有键入检查行21、24和26(实际上它只键入了一次,但是后来我更改了代码,此后没有)。

如果我将第7行更改为_the_answer : int = cast(int, None),则mypy完全保持沉默,并且没有任何警告。

版本:

mypy   0.720
Python 3.6.8
Run Code Online (Sandbox Code Playgroud)

JL *_*ret 0

经过一夜的睡眠,我得到了解决方案:将打字职责与看守职责分开。

\n\n
    \n
  • 保护变量self/cls._the_answer是无类型的。

  • \n
  • 在第 15 行,在告诉我实例尚未计算该属性的保护条件下,我键入提示一个新变量名称answer

  • \n
\n\n

21 和 24 中的两个错误分配现已被标记。

\n\n
from typing import cast\nclass Book:\n    def __init__(self, title):\n        self.title = title\n\n    _the_answer = None\n\n    def __str__(self):\n        return "%s => %s" % (self.title, self.the_answer)\n\n    @property\n    def the_answer(self)->int:\n        """always should be an int.  Not an Optional int"""\n        if self._the_answer is None:\n            answer : int  #line 15:    this is where the typing happens\n            if "Guide" in self.title:\n                #this is OK  \xe2\x9c\x85\n                answer = 42\n            elif "Woolf" in self.title:\n                #this is wrong  \xe2\x9d\x8c\n                answer = "?, I haven\'t read it"  # line 21\n            else:\n                #mypy should also flag this  \xe2\x9d\x8c\n                answer = None #line 24\n            self._the_answer = answer\n        return cast(int, self._the_answer) #line 26\n\nprint(Book("Hitchhiker\'s Guide"))\nprint(Book("Who\'s afraid of Virginia Woolf?"))\nprint(Book("War and Peace"))\n
Run Code Online (Sandbox Code Playgroud)\n\n

与之前相同的实际运行输出。

\n\n

mypy 输出:

\n\n

现在,两条问题线都已标记;-)

\n\n
test_prop4.py:21: error: Incompatible types in assignment (expression has type "str", variable has type "int")\ntest_prop4.py:24: error: Incompatible types in assignment (expression has type "None", variable has type "int")\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么cast第 26 行呢?answer如果if 语句中没有任何赋值怎么办?如果没有cast,我可能会收到警告。我可以像第 15 行那样指定一个默认值吗answer : int = 0

\n\n

我可以,但除非确实有一个好的应用程序级默认值,否则我宁愿让事情在执行时立即爆炸(UnboundLocalError在回答时),而不是让 mypy 高兴但不得不追寻一些奇怪的运行时值。

\n\n

最后一点完全取决于您的应用程序的期望,默认值可能是更好的选择。

\n