考虑这个类,它有一个变量
state
,在方法check_twice
中比较两次,并且它的值在方法work
的两次检查之间发生变化:
import enum
class State(enum.Enum):
INIT = 0
DONE = 1
class Worker:
def __init__(self) -> None:
self.state = State.INIT
def work(self):
self.state = State.DONE
def check_twice(self) -> None:
if self.state is State.DONE:
print("DONE at first try")
return
self.work()
if self.state is State.DONE:
# according to mypy this will not happen
x: int = "x" # mypy will not check this
print("DONE at second try")
return
print("not DONE after second try")
if __name__ == "__main__":
w = Worker()
w.check_twice()
正如预期的那样,执行此操作时,输出为
$ python3 volatile.py
DONE at second try
因为在
check_twice
开始时,变量仍然是 INIT
,但在第二次检查时,它已被 DONE
“在后台”更改为 work()
。
但是,根据mypy(版本1.5.0),当使用
--strict-equality
选项时,打印此输出的分支是无法访问的,
$ mypy --strict-equality volatile.py
volatile.py:23: error: Non-overlapping identity check (left operand type: "Literal[State.INIT]", right operand type: "Literal[State.DONE]") [comparison-overlap]
因为 mypy 认为如果
state
在第一次检查时为 DONE
,则该方法返回,并且在该方法的其余部分中 state
的唯一可能值是 INIT
(通过类型缩小)。它没有考虑到该值仍然可以通过其他代码更改为 DONE
并“优化掉”第二个 if self.state is State.DONE
语句。
可以通过添加
# type: ignore [comparison-overlap]
来消除该错误。但是,mypy 仍然不会检查该 if
语句的其余部分,其中包含明显的类型错误,并且不会生成错误消息。
我们如何向 mypy(或其他类型检查器)指示变量在访问一次后可以更改其值,并且必须检查第二个
if
语句的主体,类似于例如C语言中的volatile
关键字?
可以通过将比较移至(本地)函数来强制再次评估变量:
async def check_twice(self) -> None:
def done() -> bool:
return self.state is State.DONE
if done():
print("DONE at first try")
return
await asyncio.sleep(0.2)
if done():
x: int = "x" # mypy will now check this
print("DONE at second try")
return
print("not DONE after second try")
现在 mypy 将报告第二个
if
语句中的类型错误:
$ mypy --strict-equality volatile.py
volatile.py:32: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]