如何让可变变量在静态类型检查中被视为volatile?
如何让类型检查器识别变量可能被外部修改(类似C语言的volatile)
问题场景
现有如下代码,Worker类的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: # mypy 认为此分支不可达,不会检查下面的类型错误 x: int = "x" 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
但使用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,因此第二个if self.state is State.DONE分支被标记为不可达,甚至不会检查分支内明显的类型错误(比如x: int = "x")。
解决方法
1. 显式重置变量类型提示
在调用work()之后,给self.state添加类型注释,强制mypy重新识别变量的完整类型:
def check_twice(self) -> None: if self.state is State.DONE: print("DONE at first try") return self.work() self.state: State # 重置类型,告知mypy变量可能已被修改 if self.state is State.DONE: x: int = "x" # 此时mypy会检查到此处的类型错误 print("DONE at second try") return print("not DONE after second try")
这样mypy会放弃之前的类型收窄结果,重新将state视为State枚举类型,从而检查后续分支的代码。
2. 使用typing.cast强制转换类型
通过cast将self.state强制转换为State类型,打破之前的类型收窄:
from typing import cast # ... def check_twice(self) -> None: if self.state is State.DONE: print("DONE at first try") return self.work() self.state = cast(State, self.state) # 强制重置类型 if self.state is State.DONE: x: int = "x" # mypy会检测到类型不匹配 print("DONE at second try") return print("not DONE after second try")
此方法和第一种逻辑类似,都是让mypy重新识别变量的类型范围。
3. 将变量封装为属性并添加类型标注
如果希望从根源上让类型检查器意识到变量可能被外部修改,可以将state封装为@property,并显式标注其类型为State:
class Worker: def __init__(self) -> None: self._state = State.INIT @property def state(self) -> State: return self._state 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: x: int = "x" # mypy会正常检查此处错误 print("DONE at second try") return print("not DONE after second try")
使用属性后,每次访问self.state都会被视为调用属性方法,mypy不会对其进行类型收窄,自然会考虑到变量可能被修改的情况。
内容的提问来源于stack exchange,提问作者mkrieger1
相关产品推荐
相关产品推荐

