以下代码存储在名为 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类型检查器的行为不如我期望的异常处理。) / p>
答案 0 :(得分:1)
Mypy并没有真正深入地理解异常-在这种情况下,由于您正在捕获AttributeError,因此无法理解,它可以忽略“如果regex.match(path)
为None怎么办?”情况。
更一般地说,mypy做出的基本假设是,当您拥有某个类型为foo
的对象Union[A, B]
并且执行了foo.bar()
时,类型A
和{{1 }}使用B
方法。
如果这些类型中只有一种具有bar()
方法,则需要执行以下操作之一:
bar()
检查... x is not None
注释,找到使# type: ignore
成为动态foo
类型的方法... (在这种特殊情况下,我想另一种选择可能是向mypy提交拉取请求,以添加对此模式的支持。但是我不确定这是否真的可行:更改任何基本假设都很难多个维度。)
同样,Mypy也无法深入了解正则表达式-例如不会尝试分析您的正则表达式来确定您将获得多少组,因此不会理解您的特定正则表达式恰好将字符串与两个组完全匹配。最好的办法是断言该组将返回一些未知数量的字符串-因此类型为Any
而不是Sequence[str]
。
通常,这种限制在类型检查器中很常见,实际上:大多数主流语言的类型系统实际上并不支持基于传入的任何实际值的内容来确定返回类型的方法。此类类型系统(从属类型系统,优化类型系统...)很难实施,并且对于最终用户而言通常具有陡峭的学习曲线。
不过, 会更容易通过编写mypy plugin使Tuple[str, str]
和get_method_hook()
。