具有抽象方法的 Python mixin 会覆盖具体方法

ale*_*nov 7 python multiple-inheritance mixins method-resolution-order

两个 mixin 类将需求指定为抽象方法。这些类共同拥有一整套具体方法。然而,它们无法组合成一个具体的类:无论我使用哪种顺序来声明具体的类,一些抽象方法都会覆盖具体的方法。

有没有办法防止抽象方法覆盖具体方法?我相信这在 Scala 中是有效的。

指定要求的替代方法有哪些?

这是一个具体的例子:

import abc


class A(abc.ABC):
    @abc.abstractmethod
    def x(self):
        pass

    def y(self):
        return "A.y"


class B(abc.ABC):
    @abc.abstractmethod
    def y(self):
        pass

    def x(self):
        return f"B.x"


class AB(B, A):
    pass


class BA(A, B):
    pass


ab = AB()  # TypeError: Can't instantiate abstract class AB with abstract methods y

ba = BA()  # TypeError: Can't instantiate abstract class BA with abstract methods x
Run Code Online (Sandbox Code Playgroud)

blh*_*ing 1

这里的问题是,当为一个或多个抽象类的子类计算抽象方法时,只会查找子类本身,而不是所有基类,以确定抽象方法是否已被具体定义。

因为我们从PEP-3119中知道抽象类的抽象方法的名称存储在属性中__abstractmethods__,并且只有抽象方法的属性__isabstractmethod__设置为True

装饰@abstractmethod器将函数属性设置 __isabstractmethod__为值 True。该ABCMeta.__new__方法将 type 属性计算为具有值为 true 的属性__abstractmethods__的所有方法名称的集合。__isabstractmethod__

在实例化该类后,我们可以ABCMeta.__new__使用额外的逻辑进行覆盖,以检查原始计算中被认为保持抽象的方法是否实际上可以在任何基类中具体定义,在这种情况下,可以在子类中定义对它们的引用,并将它们从__abstractmethods__子类中删除:

class ComprehensiveABCMeta(abc.ABCMeta):
    _NOTFOUND = object()

    def __new__(metacls, name, bases, namespace, /, **kwargs):
        cls = super().__new__(metacls, name, bases, namespace, **kwargs)
        abstracts = set(cls.__abstractmethods__)
        for name in cls.__abstractmethods__:
            for base in bases:
                value = getattr(base, name, cls._NOTFOUND)
                if not (value is cls._NOTFOUND or
                        getattr(value, '__isabstractmethod__', False)):
                    setattr(cls, name, value)
                    abstracts.remove(name)
                    break
        cls.__abstractmethods__ = frozenset(abstracts)
        return cls

class ComprehensiveABC(metaclass=ComprehensiveABCMeta):
    pass
Run Code Online (Sandbox Code Playgroud)

以便:

class A(ComprehensiveABC):
    @abc.abstractmethod
    def x(self):
        pass

    def y(self):
        return "A.y"


class B(ComprehensiveABC):
    @abc.abstractmethod
    def y(self):
        pass

    def x(self):
        return "B.x"

class AB(B, A):
    pass


ab = AB()
print(ab.x())
print(ab.y())
Run Code Online (Sandbox Code Playgroud)

输出:

B.x
A.y
Run Code Online (Sandbox Code Playgroud)

演示: https: //ideone.com/jFMuII