Python:子类化整个层次结构

3

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


我不确定标题应该如何最好地表达。我的想法如下。我有一个包含一些实现的抽象基类。其中一些实现在其逻辑中相互引用,简化如下:

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

现在我想在另一个模块中使用这些类,但是我想要添加一些额外的功能(例如函数g)到层次结构中的所有类中。我可以通过将其添加到基类X来实现,但我想将功能定义分开。例如,我可能想像这样定义新功能:

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

这将创建另一个具有新功能的基类,但当然不会将其添加到现有实现 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的实例。为了使我的想法正常工作,这个实例在Y2.__init__中应该变成Y1。但现实并非如此:

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

我认为我可能正在寻找一种方法将X转换为抽象元类,这样其实现就需要一个“base”参数来成为类,然后可以被实例化。然后在类内部使用此参数来实例化其他具有正确基类的实现。

使用新功能创建基类的实例将如下所示:

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

X2(Y)()

这将导致一个对象,相当于以下类的实例:
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'

然而我不知道如何创建一个可以执行此操作的元类,我想听听元类是否是正确的选择,如果是,应该如何实现。


解决方案

参考MisterMiyagi的示例,我能得到一个类似于我的元类想法的行为。

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'>

有没有可能让 X2 继承自 X1 而不是实例化它(使用 super().f() + 'X2')?这样你就可以简单地增强 X1,就这样。 - a_guest
不同的Xi实现之间的行为实际上是非常不同的,因此让它们从彼此继承而不是从基类继承是没有意义的。另外,正如我所说,我不想将这个新功能添加到X家族中。这是我想要添加在其顶部作为Y家族的新功能,同时保留原始的X家族。我想这样做是为了分离功能,并且也有可能拥有一个Z家族,它也建立在X之上,但与Y无关。 - user2124834
那么,对于一般情况下使用元类如何呢?你可以将家族作为参数传递,例如 class Y(X, family='Y')。然后你可以在元类的 __new__ 中相应地调整变量。 - a_guest
我认为这可能接近我所想的,但我不知道如何实现它。例如,我不知道family参数放在哪里,也不知道为什么'Y'是一个字符串? - user2124834
从您的描述来看,不同类层次结构之间存在一些重度耦合。您确定要寻找一个特殊化,即“class Y2(Y,X2 [Y1])”,而不是装饰,即“class Y2(Y(X2(Y1)))”吗? - MisterMiyagi
显示剩余2条评论
2个回答

2
既然您正在寻找一种自定义课程的方式,最简单的方法就是这样做:
class X2(X):
    component_type = X1

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

class Y2(X2, Y):
    component_type = Y1

由于component_type是一个类属性,它允许专门化同一类的不同变体(即子类)。
请注意,您当然可以使用其他代码来构建这样的类。类方法可用于创建新的派生类。
例如,假设您的类能够从它们的层次结构中选择正确的子类
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]

类属性通常用作元类字段的功能,因为它们恰好表达了这一点。理论上,设置一个类属性等同于添加一个元类字段,然后将其作为每个类的属性进行设置。这里利用的是Python允许实例具有属性而无需类定义其字段的特性。可以将类属性视为鸭子类型的元类。

谢谢!虽然这样做的问题是我实际上有类X1...Xn,它们都需要自己的Y1...Yn,并且那些都需要知道彼此的所有内容,因此每个都需要属性component_type_1...component_type_n。我希望有一个不那么手动和冗长的解决方案。 - user2124834
@Marein 这是一个非常宽泛的描述。X1...Xn是手动定义还是生成的?n是否总是相同的?Xn需要所有的Xn-1, Xn-2, ...还是只需要Xn-1Xn, Xn-1, ...关系是类固有的,还是更适合由外部工厂处理? - MisterMiyagi
抱歉表述不够清晰。我的意思是,在我的示例中,我只有X1X2,但在我的实际用例中,我有更多的X实现。它们是手动定义的,目前为n=4。我的观点是,定义所有组件类型变得非常冗长(n^2)。 - user2124834
Xi 类在其功能的一部分中引用并实例化特定的其他 Xj 类。根据它们的功能,不是每个 Xi 类都必须使用每个其他的 Xj 类,但它们可能会使用。如果需要,我可以提供实际用例的概述。 - user2124834
1
我想我能够在你的示例帮助下完成它!我已经编辑了问题以展示结果。 - user2124834
显示剩余2条评论

2
您的问题很常见,即您想改变现有类的行为。通过继承并添加新行为,您可以实现其中一个方面。然后,您创建的所有此子类的实例都具有新行为。
但是您还希望其他人(在这种情况下是X2)创建更多此类的实例现在改为创建具有添加行为的自己的子类的实例。
这可以被认为是干涉他人的事务。我的意思是,如果类X2想要创建类X1的实例,那么您(只是X2的用户!)告诉它应该创建其他东西是谁? 也许它与不是类型X1的某些内容无法正常工作!
但当然。做过。我知道有时会出现这种需求。
直接实现这一点的方法是使类X2合作。这意味着,它可以不创建类X1的实例,而是创建传递的类的实例作为参数:
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

类似的解决方案可以使用单元测试模块中所知的模拟来实现。但所有这些方案都有一个共同点,即它们适用于所使用类的当前实现的细节。一旦这些实现发生变化,代码就会出现问题。
因此,如果可能的话,最好准备好项目的基础类(X2),并使其更加灵活以适应您的用例。

这可以被视为干涉他人的事务。这就是为什么我认为一个带有“base”参数的元类可能是一个好主意,因为X本身就暴露了子类化外部类的可能性。使用参数传递/方法覆盖解决方案,我仍然需要手动定义所有Xi实现的Yi变体,并且我需要将它们全部作为参数传递。我希望有一个不那么手动/冗长的解决方案。 - user2124834

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接