以下代码存储在名为 sample.py 的文件中。
import re
from typing import Optional, Tuple
def func(path: str) -> Optional[Tuple[str, str]]:
regex = re.compile(r"/'([^/']+?)'/'([^/']+?)'")
try:
return regex.match(path).groups()
except AttributeError:
return None
Mypy Python linter 在分析代码时抛出以下错误:
sample.py:8: error: Incompatible return value type (got "Union[Sequence[str], Any]", expected "Optional[Tuple[str, str]]")
sample.py:8: error: Item "None" of "Optional[Match[str]]" has no attribute "groups"
虽然
regex.match(path).groups()
可能返回 None
类型,但该类型没有 groups
属性,但会处理生成的异常,并在返回类型中指定处理方式。然而,Mypy 似乎不明白异常正在被处理。据我了解 Optional[Tuple[str, str]]
是正确的返回类型,而 Mypy 坚持认为不太具体的类型 Union[Sequence[str], Any]
是正确的。在 Python 类型中使用异常处理的正确方法是什么? (请注意,我并不是要求在不使用异常处理的情况下编写代码的替代方法。我只是想提供一个最小且完整的示例,其中 Python 类型检查器的行为与我期望的异常处理不同。)
Mypy 并没有真正理解深层次的异常——在这种情况下,不理解因为你捕获了 AttributeError,所以它可以忽略“如果
regex.match(path)
是 None 会怎样?”案例。
更一般地说,mypy 所做的基本假设是,当您有某个类型为
foo
的对象 Union[A, B]
并且您执行 foo.bar()
时,A
和 B
类型都有一个 bar()
方法。
如果这些类型中只有一种具有
bar()
方法,则您需要执行以下操作之一:
x is not None
检查...# type: ignore
注释,找到一种使 foo
成为动态 Any
类型的方法...(在这种特殊情况下,我认为另一种选择可能是向 mypy 提交拉取请求,添加对此模式的支持。但我不确定这是否真的可行:改变任何类型的基本假设在多个维度上都是困难的工作.)
同样,Mypy 也无法深入理解正则表达式——例如不会尝试分析您的正则表达式来确定您将获得多少个组,因此不会理解您的特定正则表达式恰好与两个组的字符串匹配。它能做的最好的事情就是断言该组将返回一些未知数量的字符串 - 因此类型为
Sequence[str]
而不是 Tuple[str, str]
。
实际上,这种限制在类型检查器中非常常见:主流语言中的大多数类型系统并不真正支持基于传入的任何实际值的内容来谓词返回类型的方法。此类类型系统(依赖于类型系统、细化类型系统...)很难实现,并且对于最终用户来说通常有一个陡峭的学习曲线。
但是,如果您愿意的话,通过编写一个 mypy 插件,让 mypy 在“尽力而为”的基础上支持这一点会更容易。具体来说,请尝试查看 get_method_hook()
和 get_function_hook()
。