Python:子类整个层次结构

Posted

技术标签:

【中文标题】Python:子类整个层次结构【英文标题】:Python: Subclass whole hierarchy 【发布时间】:2019-02-28 23:56:35 【问题描述】:

我的解决方案在问题的底部,基于 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 转换为抽象元类的方法,这样它的实现需要一个“基础”参数才能成为类,然后可以对其进行实例化。然后在类中使用此参数来实例化具有正确基础的其他实现。

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

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 就可以了。 不同Xi 实现之间的行为在现实中是完全不同的,所以我认为让它们从彼此继承而不是从基类继承是没有意义的。另外,正如我所说,我不想将此新功能添加到X 系列。这是我想在其之上添加的新功能,作为Y 系列,同时保留原始X 系列。我想这样做是为了分离功能,并有可能拥有一个Z 系列,它也建立在X 之上,但与Y 无关。 那么一般来说如何使用元类,您可以将族作为参数传递,例如class Y(X, family='Y')。然后你可以在元类__new__中相应地调整变量。 我认为这可能接近我的想法,但我不知道如何实现它。例如我不知道family 参数去哪里或者为什么'Y' 是一个字符串? 按照您的描述,不同的类层次结构之间存在一些耦合。您确定要寻找专业,即class Y2(Y, X2[Y1]),而不是装饰,即class Y2(Y(X2(Y1))) 【参考方案1】:

由于您正在寻找一种自定义类的方法,因此最简单的方法就是这样做:

class X2(X):
    component_type = X1

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

class Y2(X2, Y):
    component_type = Y1

由于component_type 是一个类属性,它允许专门化同一类的不同变体(阅读:子类)。


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

假设你的班级能够选择正确的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]

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

【讨论】:

谢谢!虽然这样做的问题是我实际上有类X1...Xn,它们都需要自己的Y1...Yn,而且它们都需要了解彼此的所有信息,所以每个都需要attributescomponent_type_1...component_type_n。我希望有一个不那么手动和冗长的解决方案。 @Marein 这是一个非常松散的描述。 X1...Xn 来自哪里 - 它们是手动定义的还是生成的? n 总是一样吗? Xn 需要全部 Xn-1, Xn-2, ... 还是只需要 Xn-1Xn, Xn-1, ... 关系是类所固有的,还是更适合由外部工厂处理? 抱歉含糊不清。我的意思是,在我的示例中,我只有X1X2,但在我的实际用例中,我有更多X 的实现。它们是手动定义的,目前为n=4。我的观点是 (n^2) 定义所有组件类型变得非常冗长。 我知道您有更多课程,但完全不清楚它们之间的关系。例如,如果每个Xn 需要所有其他X1...Xn,它可以从它的基类中获取这些。相反,如果每个Xn 都需要一个特定的Xn-1,它必须知道它。 我想我能够在你的例子的帮助下做到这一点!我已经编辑了问题以显示结果。【参考方案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 本身就暴露了对外部类进行子类化的可能性。使用参数传递/方法覆盖解决方案,我仍然需要手动定义所有Yi 实现的所有Yi 变体。我需要将它们全部作为参数传递。我希望有一个不那么手动/冗长的解决方案。

以上是关于Python:子类整个层次结构的主要内容,如果未能解决你的问题,请参考以下文章

子类化 UIButton 并将 UILabel 添加到视图层次结构会导致 currentImage 压缩

Python 子类继承父类

Java 中类型层次结构的效率

漫谈设计模式:桥接(Bridge)模式 —— 将类功能结构两层次分离

子类化 UIAlertController

Visual studio 中,如何查看 指定的C++类结构层次(所有父类,所有子类)