如何解决用子类型覆盖子类型中的参数类型
我有一个类型和一个子类型,它们以这样一种方式定义了一些二元运算,我希望该运算能够返回最具体的类型。例如,在下面的代码示例中,我期望有以下行为。
Logic + Logic => Logic
Logic + Bit => Logic
Bit + Logic => Logic
Bit + Bit => Bit
示例:
class Logic:
"""
4-value logic type: 0,1,X,Z
"""
def __and__(self,other: 'Logic') -> 'Logic':
if not isinstance(other,Logic):
return NotImplemented
# ...
def __rand__(self,other: 'Logic') -> 'Logic':
return self & other
class Bit(Logic):
"""
2-value bit type: 0,1
As a subtype of Logic,Logic(0) == Bit(0) and hash(Logic(0)) == hash(Bit(0))
"""
def __and__(self,other: 'Bit') -> 'Bit':
if not isinstance(other,Bit):
return NotImplemented
# ...
def __rand__(self,other: 'Bit') -> 'Bit':
return self & other
虽然这在运行时有效,但 mypy 抱怨:
example.pyi:19: error: Argument 1 of "__and__" is incompatible with supertype "Logic"; supertype defines the argument type as "Logic"
example.pyi:19: note: This violates the Liskov substitution principle
example.pyi:19: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
我如何表达这种关系?我觉得这个问题可能与 Python 不支持 OOTB 支持多分派的事实有关,并且在 mypy 的类型系统中以一种我无法表达的方式进行编码。可能是 mypy 在这里太挑剔了,这应该没问题,因为 Bit
中提到的 Bit.__and__
参数是 Logic
的子类型,因此“不兼容”是不正确的。>
解决方法
您正在创建一个子类并从您继承的类中覆盖一个函数。这很好,只要您不更改签名(这就是 mypy 抱怨的原因)。有几种可能的解决方案,但您可以试试这个:
from __future__ import annotations
class Base:
...
class Logic:
"""
4-value logic type: 0,1,X,Z
"""
def __and__(self,other: Base) -> Logic:
if not isinstance(other,Logic):
raise NotImplementedError()
return self
def __rand__(self,other: Base) -> Logic:
return self & other
class Bit(Logic):
"""
2-value bit type: 0,1
As a subtype of Logic,Logic(0) == Bit(0) and hash(Logic(0)) == hash(Bit(0))
"""
def __and__(self,other: Base) -> Bit:
if not isinstance(other,Bit):
raise NotImplementedError()
return self
def __rand__(self,other: Base) -> Bit:
return self & other
,
这是 typing.overload
的完美用例。您可以为同一个函数指定多个签名并将它们标记为 @overload
,mypy 将解析对适当重载的调用。
对于您的示例,您可以重载 __add__
类中的 __radd__
和 Bit
方法(请参阅 mypy-play 上的 mypy 输出):
from typing import overload
class Logic:
def __and__(self,other: 'Logic') -> 'Logic':
pass
class Bit(Logic):
@overload
def __and__(self,other: 'Bit') -> 'Bit': ...
@overload
def __and__(self,other: 'Logic') -> 'Logic': ...
def __and__(self,other):
pass
reveal_type(Logic() & Logic()) # Logic
reveal_type(Logic() & Bit()) # Logic
reveal_type(Bit() & Logic()) # Logic
reveal_type(Bit() & Bit()) # Bit
请注意,重载是按顺序匹配的,因此 Bit
重载必须出现在 Logic
重载之前。如果你反过来写,你会收到来自 mypy 的警告。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。