繁体   English   中英

mypy错误,使用Union / Optional重载,“重载的函数签名1和2与不兼容的返回类型重叠”

[英]mypy error, overload with Union/Optional, “Overloaded function signatures 1 and 2 overlap with incompatible return types”

那么,让我们从一个例子开始吧。 假设我们有几种类型可以组合在一起,假设我们使用__add__来实现它。 不幸的是,由于我们无法控制的情况,一切都必须“可以为空”,因此我们不得不在任何地方使用Optional

from typing import Optional, List, overload
class Foo:
    value: int
    def __init__(self, value: int) -> None:
        self.value = value
    def __add__(self, other: 'Foo') -> 'Optional[Foo]':
        result = self.value - other.value
        if result > 42:
            return None
        else:
            return Foo(result)

class Bar:
    value: str
    def __init__(self, value: str) -> None:
        self.value = value
    def __add__(self, other: 'Bar') -> 'Optional[Bar]':
        if len(self.value) + len(other.value) > 42:
            return None
        else:
            return Bar(self.value + other.value)

class Baz:
    value: List[str]
    def __init__(self, value:List[str]) -> None:
        self.value = value
    def __add__(self, other: 'Bar') -> 'Optional[Baz]':
        if len(self.value) + 1 > 42:
            return None
        else:
            return Baz([*self.value, other.value])


@overload
def Add(this: Optional[Foo], that: Optional[Foo]) -> Optional[Foo]:
    ...
@overload
def Add(this: Optional[Bar], that: Optional[Bar]) -> Optional[Bar]:
    ...
@overload
def Add(this: Optional[Baz], that: Optional[Bar]) -> Optional[Baz]:
    ...
def Add(this, that):
    if this is None or that is None:
        return None
    else:
        return this + that

我们希望实用程序函数对我们执行空值检查,但是通常可以处理“可组合”类型。 大多数类型只能与自己结合使用,为了更加真实我的实际用例,假设一种类型与另一种类型相结合。 我希望overload装饰器可以在这里帮助,但mypy抱怨:

mcve4.py:35: error: Overloaded function signatures 1 and 2 overlap with incompatible return types
mcve4.py:35: error: Overloaded function signatures 1 and 3 overlap with incompatible return types
mcve4.py:38: error: Overloaded function signatures 2 and 3 overlap with incompatible return types

使用mypy版本: mypy 0.641

请注意,如果我删除Optional疯狂,mypy不会抱怨。 我甚至可以将其中一个作为选项!:

from typing import List, overload
class Foo:
    value: int
    def __init__(self, value: int) -> None:
        self.value = value
    def __add__(self, other: 'Foo') -> 'Foo':
        result = self.value - other.value
        return Foo(result)

class Bar:
    value: str
    def __init__(self, value: str) -> None:
        self.value = value
    def __add__(self, other: 'Bar') -> 'Bar':
        return Bar(self.value + other.value)

class Baz:
    value: List[str]
    def __init__(self, value:List[str]) -> None:
        self.value = value
    def __add__(self, other: 'Bar') -> 'Optional[Baz]':
        return Baz([*self.value, other.value])


@overload
def Add(this: Foo, that: Foo) -> Foo:
    ...
@overload
def Add(this: Bar, that: Bar) -> Bar:
    ...
@overload
def Add(this: Baz, that: Bar) -> 'Optional[Baz]':
    ...
def Add(this, that):
    if this is None or that is None:
        return None
    else:
        return this + that

这让我怀疑“重叠”是针对NoneType的,但感觉这应该是可解析的,我完全偏离基础吗?

编辑

所以,我真的只是在这里喋喋不休,但我想,当两个论点都是None这肯定是模棱两可的,我希望以下会解决它:

@overload
def Add(this: None, that: None) -> None:
    ...
@overload
def Add(this: Optional[Foo], that: Optional[Foo]) -> Optional[Foo]:
    ...
@overload
def Add(this: Optional[Bar], that: Optional[Bar]) -> Optional[Bar]:
    ...
@overload
def Add(this: Optional[Baz], that: Optional[Bar]) -> Optional[Baz]:
    ...
def Add(this, that):
    if this is None or that is None:
        return None
    else:
        return this + that

但我仍然得到:

mcve4.py:37: error: Overloaded function signatures 2 and 3 overlap with incompatible return types
mcve4.py:37: error: Overloaded function signatures 2 and 4 overlap with incompatible return types
mcve4.py:40: error: Overloaded function signatures 3 and 4 overlap with incompatible return types

Edit2沿着同一条花园小径走,我尝试了以下方法:

@overload
def Add(this: None, that: None) -> None:
    ...
@overload
def Add(this: Foo, that: Optional[Foo]) -> Optional[Foo]:
    ...
@overload
def Add(this: Optional[Foo], that: Foo) -> Optional[Foo]:
    ...
@overload
def Add(this: Baz, that: Bar) -> Optional[Baz]:
    ...
@overload
def Add(this: Baz, that: Optional[Bar]) -> Optional[Baz]:
    ...
@overload
def Add(this: Optional[Baz], that: Bar) -> Optional[Baz]: # 6
    ...
@overload
def Add(this: Bar, that: Optional[Bar]) -> Optional[Bar]:
    ...
@overload
def Add(this: Optional[Bar], that: Bar) -> Optional[Bar]: # 8
    ...

def Add(this, that):
    if this is None or that is None:
        return None
    else:
        return this + that

我现在得到:

mcve4.py:49: error: Overloaded function signatures 6 and 8 overlap with incompatible return types

这开始对我有意义,我认为从根本上说我想做的是不安全/破碎。 我可能不得不以另一种方式切割戈尔迪结...

解决这个问题不会有一个特别干净的方法,我担心 - 至少,我个人都不知道。 正如您所观察到的,您的类型签名包含mypy不允许的基本歧义:如果您尝试使用类型为None的参数调用Add ,mypy将从根本上无法推断出哪个给定的重载变体匹配。

有关这方面的更多讨论,请参阅有关检查过载不变量的mypy文档 - 搜索讨论“固有不安全重叠变体”的段落并从那里开始阅读。


但是,我们可以通过拼写重载来更精确地匹配实际的运行时行为,从而在这种特定情况下自由摆动。 特别是,我们有这个不错的属性,如果任一参数是'None',我们还必须返回None。 如果我们对此进行编码,mypy最终会满意:

@overload
def Add(this: None, that: None) -> None:
    ...
@overload
def Add(this: Foo, that: None) -> None:
    ...
@overload
def Add(this: Bar, that: None) -> None:
    ...
@overload
def Add(this: Baz, that: None) -> None:
    ...
@overload
def Add(this: None, that: Foo) -> None:
    ...
@overload
def Add(this: None, that: Bar) -> None:
    ...
@overload
def Add(this: Foo, that: Foo) -> Foo:
    ...
@overload
def Add(this: Bar, that: Bar) -> Bar:
    ...
@overload
def Add(this: Baz, that: Bar) -> Baz:
    ...
def Add(this, that):
    if this is None or that is None:
        return None
    else:
        return this + that

x: Optional[Baz]
y: Optional[Bar]

reveal_type(Add(x, y))  # Revealed type is 'Union[Baz, None]'

事实上这可能最初看起来令人惊讶 - 毕竟,我们传入一个类型为Optional[...]的参数,但没有任何重载包含该类型!

mypy在这里做的是非正式地称为“联合数学” - 它基本上观察到xy分别是Union[Baz, None]Union[Bar, None]类型的Union[Baz, None] ,并尝试做一个嵌套的精神等价物for循环检查这些联合的每个可能的组合。 因此,在这种情况下,它会检查匹配(Baz, Bar)(Baz, None)(None, Bar)(None, None)的重载变量,并返回类型Baz,None,None的返回值,和无。

最终的返回类型是这些值的并集: Union[Baz, None, None, None] 这简化为Union[Baz, None] ,这是所需的返回类型。


当然,这个解决方案的主要缺点是它非常冗长 - 可能是难以忍受的程度,取决于你拥有多少这些辅助函数以及这个“我们可以返回无”的问题在你的代码库中是多么普遍。

如果是这种情况,您可能会做的事情是在整个代码库中对“无”的普遍性声明“破产”,并在禁用“严格可选”模式的情况下开始运行mypy。

简而言之,如果使用--no-strict-optional标志运行mypy,则表示mypy假定'None'是每个类的有效成员。 这与Java假设'null'是每种​​类型的有效成员的方式相同。 (好吧,每个非原始类型,但无论如何)。

这削弱了你的代码(有时急剧)的类型安全,但它会让你简化你的代码如下所示:

class Foo:
    value: int
    def __init__(self, value: int) -> None:
        self.value = value

    # Note: with strict-optional disabled, returning 'Foo' vs
    # 'Optional[Foo]' means the same thing
    def __add__(self, other: 'Foo') -> Foo:
        result = self.value - other.value
        if result > 42:
            return None
        else:
            return Foo(result)


@overload
def Add(this: Foo, that: Foo) -> Foo:
    ...
@overload
def Add(this: Bar, that: Bar) -> Bar:
    ...
@overload
def Add(this: Baz, that: Bar) -> Baz:
    ...
def Add(this, that):
    if this is None or that is None:
        return None
    else:
        return this + that

x: Optional[Baz]
y: Optional[Bar]

reveal_type(Add(x, y))  # Revealed type is 'Baz'

严格地说,过载检查应该报告“不安全重叠”错误,原因与启用严格可选时的错误相同。 但是,如果我们这样做,当禁用strict-optional时,重载将完全无法使用:因此mypy故意削弱此处的检查并忽略该特定错误情况。

这种模式的主要缺点是你现在被迫做更多的运行时检查。 如果你收到一些Baz类型的值,它实际上可能是None - 类似于Java中的任何对象引用实际上可能为null

在您的情况下,这可能是一个很好的权衡,因为您已经在各地分散了这些类型的运行时检查。

如果您订阅“null是一个十亿美元的错误”思想学派并且希望生活在严格可选的世界中,您可以使用的一种技术是逐步重新启用严格可选的代码库的选定部分使用mypy配置文件

基本上,您可以通过配置文件在每个模块的基础上配置许多(尽管不是全部)mypy选项,如果您尝试将类型添加到预先存在的代码库并发现一次性转换,这可能非常方便简直是难以处理的。 从松散的全局设置开始,然后逐渐使它们随着时间的推移变得更加严格和严格。


如果这两个选项都感觉太极端(例如,你不想在上面添加详细的签名,但也不想放弃严格的可选),你可以做的最后一个选择就是简单使错误沉默通过添加#type # type: ignore每行mypy报告“不安全重叠类型”错误。

这在某种程度上也是失败的,但可能是更本地化的。 甚至类型化,标准库类型提示的存储库,包含一些分散的#type # type: ignore注释,这些和那里的某些函数使用PEP 484类型是无法表达的。

无论这是否是一个解决方案你都可以取决于你的具体情况。 如果你分析你的代码库,并认为潜在的不安全感是你可以忽略的,也许这可能是最简单的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM