我仍然在学习 Python,并且很早就开始使用 Mypy,以避免忘记使用正确的类型并检查代码中缺少的内容。到目前为止,大多数事情都进展顺利,我对检查器从代码中读取的内容感到惊讶,并且没有抱怨。
今天我发现了一些让我困惑的情况。使用以下非常简化的示例代码
from typing import Optional
myvar: Optional[int] = None
myvar = 42
if myvar < 1:
print("Error")
Mypy 没有什么可抱怨的,因为它显然知道
myvar
不能是 None
语句中的 if
。
如果我使用
TypedDict
,那就很奇怪了,这在下面的片段中并不成立:
from typing import Optional, TypedDict
class MyDictType(TypedDict):
mykey: Optional[int]
mydict1: MyDictType = {'mykey': None}
mydict1['mykey'] = 42
if mydict1['mykey'] < 1:
print("Error")
Mypy 在这里抱怨
Unsupported operand types for > ("int" and "None")
和 Left operand is of type "Optional[int]"
。它“迫使”我直接检查喜欢
if mydict1['mykey'] is not None and mydict1['mykey'] < 1:
print("Error")
因此我的问题是:为什么 Mypy 不能识别与
mydict1['mykey']
完全相同的东西(None
不可能是 TypedDict
)?
您的两个示例并不相同,因此由于 Python 和 Mypy 中的赋值规则而导致不同的行为。首先,您将
myvar
替换为全新的作业 42
,其类型为 int
,而不是 Optional[int]
。第二个示例不会导致对 mydict1
进行全新的分配(因为类型检查最终是针对基础名称的分配进行的 - 将新值分配给键 'mykey'
只会影响 mydict1
内的不透明内部值,无需对 mydict1
的分配进行实质性更改。但这仅回答了部分问题,因为 Mypy 会正确标记类型不匹配问题,您可以将 'forty-two'
分配给 myvar
,那么 Mypy 真正看到了什么?
reveal_type
助手。将其添加到受影响的分配之后的行中:
from typing import Optional
myvar: Optional[int] = None
reveal_type(myvar)
myvar = 42
reveal_type(myvar)
if myvar < 1:
print("Error")
通过 Mypy 运行上述内容
so78038503_1.py:4: note: Revealed type is "Union[builtins.int, None]"
so78038503_1.py:7: note: Revealed type is "builtins.int"
Success: no issues found in 1 source file
注意 Mypy 只是假设
myvar
在这种情况下变成了 int
,因此不会将简单比较标记为错误(尽管底层类型注释没有改变一点,这可以通过检查 来验证) locals()['__annotations__']
)。
对于第二个例子,做同样的
reveal_type
检查:
from typing import Optional, TypedDict
class MyDictType(TypedDict):
mykey: Optional[int]
mydict1: MyDictType = {'mykey': None}
reveal_type(mydict1['mykey'])
mydict1['mykey'] = 42
reveal_type(mydict1['mykey'])
if mydict1['mykey'] < 1:
print("Error")
检查会注意到
mydict1['mykey']
保持类型 Union[builtins.int, None]
而不会下降到 builtins.int
。
so78038503_2.py:7: note: Revealed type is "Union[builtins.int, None]"
so78038503_2.py:10: note: Revealed type is "Union[builtins.int, None]"
so78038503_2.py:12: error: Unsupported operand types for > ("int" and "None") [operator]
so78038503_2.py:12: note: Left operand is of type "int | None"
Found 1 error in 1 file (checked 1 source file)
pyright
对于第二个示例不会有任何问题,因为它假设两种情况下的最终值都是Literal[42]
,即builtins.int
,尽管我个人认为这种行为可能会导致脆性,因为正如您所指出的,通过实际的 Optional[int]
进行作业中的微妙变化以揭示缺乏对 None
的防范,这破坏了类型检查的全部意义。