微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

用子类型覆盖子类型中的参数类型

如何解决用子类型覆盖子类型中的参数类型

我有一个类型和一个子类型,它们以这样一种方式定义了一些二元运算,我希望该运算能够返回最具体的类型。例如,在下面的代码示例中,我期望有以下行为。

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 举报,一经查实,本站将立刻删除。