Python: 整个层次结构的子类

Python: Subclass whole hierarchy

我的解决方案在问题的底部,基于 MisterMiyagi 的示例


我不确定如何最好地表达标题。我的想法如下。我有一个带有一些实现的抽象基础 class。其中一些实现将彼此引用作为其逻辑的一部分,简化如下:

import abc


# the abstract class
class X(abc.ABC):
    @abc.abstractmethod
    def f(self):
        raise NotImplementedError()

# first implementation
class X1(X):
    def f(self):
        return 'X1'

# second implementation, using instance of first implementation
class X2(X):
    def __init__(self):
        self.x1 = X1()

    def f(self):
        return self.x1.f() + 'X2'

# demonstration
x = X2()
print(x.f())  # X1X2
print(x.x1.f())  # X1

现在我想在某个地方使用这些 classes,比方说在另一个模块中。但是,我想向层次结构中的所有 classes 添加一些额外的功能(例如函数 g)。我可以通过将它添加到基础 class X 来做到这一点,但我想保留单独定义的功能。例如,我可能想像这样定义新功能:

class Y(X):
    def g(self):
        return self.f() + 'Y1'

这将创建另一个具有新功能的基础 class,但当然不会将其添加到现有实现 X1X2 中。我必须使用钻石继承来获得它:

class Y1(X1, Y):
    pass

class Y2(X2, Y):
    pass

# now I can do this:
y = Y2()
print(y.g())  # X1X2Y1

以上操作正常,但仍有问题。在 X2.__init__ 中,创建了 X1 的一个实例。为了让我的想法奏效,这必须变成 Y1 in Y2.__init__。但是当然不是这样的:

print(y.x1.g())  # AttributeError: 'X1' object has no attribute 'g'

我想我可能正在寻找一种将 X 变成抽象元 class 的方法,这样它的实现需要一个 'base' 参数才能变成 classes,然后可以实例化。然后在 class 中使用此参数来实例化具有正确基础的其他实现。

在基础 class 中创建具有新功能的实例将如下所示:

class Y:
    def g(self):
        return self.f() + 'Y1'

X2(Y)()

这将导致 object 等同于以下 class 的实例:

class X2_with_Y:
    def __init__(self):
        self.x1 = X1(Y)()

    def f(self):
        return self.x1.f() + 'X2'

    def g(self):
        return self.f() + 'Y1'

但是我不知道如何创建可以执行此操作的元class。我想听听 metaclass 是否是正确的想法,如果是的话,如何去做。


解决方案

使用 MisterMiyagi 的示例,我得到了一些我认为可行的东西。该行为接近于我的 metaclass 想法。

import abc


class X(abc.ABC):
    base = object  # default base class

    @classmethod
    def __class_getitem__(cls, base):
        if cls.base == base:
            # makes normal use of class possible
            return cls
        else:
            # construct a new type using the given base class and also remember the attribute for future instantiations
            name = f'{cls.__name__}[{base.__name__}]'
            return type(name, (base, cls), {'base': base})


    @abc.abstractmethod
    def f(self):
        raise NotImplementedError()


class X1(X):
    def f(self):
        return 'X1'


class X2(X):
    def __init__(self):
        # use the attribute here to get the right base for the other subclass
        self.x1 = X1[self.base]()

    def f(self):
        return self.x1.f() + 'X2'


# just the wanted new functionality
class Y(X):
    def g(self):
        return self.f() + 'Y1'

用法是这样的:

# demonstration
y = X2[Y]()
print(y.g())  # X1X2Y1
print(y.x1.g())  # X1Y1
print(type(y))  # <class 'abc.X2[Y]'>
# little peeve here: why is it not '__main__.X2[Y]'?

# the existing functionality also still works
x = X2()
print(x.f())  # X1X2
print(x.x1.f())  # X1
print(type(x))  # <class '__main__.X2'>

由于您正在寻找自定义 classes 的方法,最简单的方法就是这样做:

class X2(X):
    component_type = X1

    def __init__(self):
        self.x1 = self.component_type()

class Y2(X2, Y):
    component_type = Y1

由于 component_type 是一个 class 属性,它允许特化相同 class.[=20= 的不同变体(阅读:subclasses) ]


请注意,您当然可以使用其他代码来构造此类 classes。 Class方法可用于创建新的派生 classes。

假设你的 classes 能够选择正确的 subclasses from their hierarchy

class X2(X):
    hierarchy = X

    @classmethod
    def specialise(cls, hierarchy_base):
        class Foo(cls, hierarchy_base):
            hierarchy = hierarchy_base
        return Foo

Y2 = X2.specialise(Y)

因为Python3.7,可以用__class_getitem__把上面写成Y2 = X2[Y],类似于Tuple如何特化到Tuple[int].

class X2(X):
    hierarchy = X

    def __class_getitem__(cls, hierarchy_base):
        class Foo(cls, hierarchy_base):
            hierarchy = hierarchy_base
        return Foo

Y2 = X2[Y]

Class 属性通常服务于 metaclass 字段的功能,因为它们准确地表达了这一点。理论上,设置一个class属性相当于添加一个metaclass字段,然后将其设置为每个class的属性。这里利用的是 Python 允许实例具有属性而无需 classes 定义它们的字段。可以将 class 属性视为鸭子类型的元 classes.

您的问题经常出现,即您想更改现有 class 的行为。您可以通过继承它并添加新行为来实现这一方面。您随后创建的此子class 的所有实例都具有新行为。

但您还希望 其他人(在本例中是 X2)创建此 进一步 实例class 现在改为使用添加的行为创建您自己的 subclass 的实例。

这也算是多管闲事了。我的意思是,如果 class X2 想要创建 X1 的实例,你是谁(只是 X2 的用户!)告诉它它应该创建别的东西??也许它不能正常使用非 X1!

类型的东西

但是——当然。到过那里。做到了。我知道有时会出现这种需求。

实现这一目标的直接方法是让 class X2 合作。这意味着,与其创建 class X1 的实例,不如创建作为参数传递的 class 的实例:

class X2(X):
    def __init__(self, x1_class=X1):
        self.x1 = x1_class()

这也可以使用方法覆盖而不是参数传递很好地嵌入:

class X2(X):
    @classmethod
    def my_x1(cls):
         return X1

    def __init__(self):
        self.x1 = self.my_x1()

然后在另一个模块中:

class Y2(X2, Y):
    @classmethod
    def my_x1(cls):
        return Y1

但是如果你能改变X2,所有这一切都有效,在某些情况下你不能这样做(因为X的模块是第三方提供的,甚至是内置库, 所以实际上是只读的)。

在这些情况下,您可以考虑猴子修补:

def patched_init(self):
    self.x1 = Y1()

X1.__init__ = patched_init

可以使用单元测试模块中已知的模拟来实现类似的解决方案。但所有这些都有一个共同点,即它们适用于所用 classes 当前实现的私密细节。一旦这些发生变化,代码就会中断。

因此,如果可以的话,最好为您的项目准备基础 classes (X2) 并使其更灵活地适应您的用例。