如何动态添加mixins作为基类而不会出现MRO错误?

时间:2012-04-12 22:16:35

标签: python class architecture method-resolution-order

说我有一个课程ABC

班级AB都是班级C的混合班级。

class A( object ):
    pass
class B( object ):
    pass
class C( object, A, B ):
    pass

在实例化类C时这不起作用。我必须从类C中删除object才能使它工作。 (否则你会遇到MRO问题)。

  

TypeError:调用元类库时出错       无法创建一致的方法解决方案
  基数B,对象,A的订单(MRO)

然而,我的情况有点复杂。在我的情况下,类C服务器,其中AB将是启动时加载的插件。它们位于自己的文件夹中。

我还有一个名为Cfactory的类。在Cfactory中,我有一个__new__方法,它将创建一个功能齐全的对象C.在__new__方法中我搜索插件,使用__import__加载它们,并且然后将它们分配给C.__bases__ += (loadedClassTypeGoesHere, )

所以以下是可能的:(使其相当抽象)

class A( object ):
    def __init__( self ): pass
    def printA( self ):   print "A"
class B( object ):
    def __init__( self ): pass
    def printB( self ):   print "B"
class C( object ):
    def __init__( self ):  pass
class Cfactory( object ):
    def __new__( cls ):
        C.__bases__ += ( A, )
        C.__bases__ += ( B, )
        return C()

这将再次起作用,并将再次给出MRO错误:

  

TypeError:无法创建一致的方法分辨率   基础对象的订单(MRO),A

一个简单的解决方法是从objectA删除B基类。然而,这将使它们成为旧式对象,当这些插件独立运行时应该避免(这应该是可能的,UnitTest明智的)

另一个简单的解决方法是从object删除C,但这也会使它成为旧式类,C.__bases__将不可用,因此我无法向基础添加额外的对象C

对于这个有什么好的架构解决方案,你会怎么做这样的事情?现在我可以使用插件本身的旧式类。但我宁愿不使用它们。

3 个答案:

答案 0 :(得分:11)

以这种方式思考 - 您希望mixins覆盖object的某些行为,因此它们需要在方法解析顺序中位于object之前。

所以你需要改变基数的顺序:

class C(A, B, object):
    pass

由于this bug,您需要C不要直接从对象继承才能正确分配给__bases__,而工厂真的可能只是一个函数:

class FakeBase(object):
    pass

class C(FakeBase):
    pass

def c_factory():
    for base in (A, B):
        if base not in C.__bases__:
            C.__bases__ = (base,) + C.__bases__
    return C()

答案 1 :(得分:4)

我不知道细节,所以也许我完全偏离了这里,但看起来你正在使用错误的机制来实现你的设计。

首先,为什么Cfactory是一个类,为什么它的__new__方法返回其他东西的实例?这看起来像是一种奇怪的方式来实现一个很自然的功能。正如你所描述的那样Cfactory(并显示了一个简化的例子)并不像一个类那样表现;你没有多个共享功能的实例(实际上看起来你已经无法自然地构造实例了)。

说实话,C对我来说看起来也不像是一堂课。看起来你不能创建它的多个实例,否则你最终会得到一个不断增长的基础列表。因此,C基本上是一个模块而不是一个类,只有额外的样板。我试图避免“单实例类来表示应用程序或某些外部系统”模式(虽然我知道它很受欢迎,因为Java 需要你使用它)。但是类继承机制通常可以用于非真正类的事情,例如你的插件系统。

我会在C上使用classmethod来查找和加载插件,由定义C的模块调用,以便始终处于良好状态。或者,您可以使用元类自动将它找到的任何插件添加到类库中。将用于配置的机制与用于创建类实例的机制混合似乎是错误的;它与柔性解耦设计相反。

如果在创建C时无法加载插件,那么我会在您可以搜索插件之前手动调用配置器类方法。 <{1}}实例已创建。

实际上,如果类一旦创建就无法进入一致状态,我宁可去创建动态类而不是修改现有类的基础。然后系统不会被锁定到被配置一次的类并实例化一次;你至少可以开始使用不同的插件集加载多个实例。像这样:

C

这样,您可以通过单次调用来创建def Cfactory(*args, **kwargs): plugins = find_plugins() bases = (C,) + plugins cls = type('C_with_plugins', bases, {}) return cls(*args, **kwargs) 实例,并为您提供正确配置的实例,但它对C的任何其他假设实例没有奇怪的副作用已存在,其行为并不取决于它是否已经运行过。我知道你可能不需要这两个属性中的任何一个,但它只是你的简化示例中的代码,并且为什么要破坏你不需要的类的概念模型?

答案 2 :(得分:3)

有一个简单的解决方法:创建一个名为helper-class的类,如 PluginBase 。并使用继承而不是对象。

这使得代码更具可读性(imho),并且它存在错误。

class PluginBase(object): pass
class ServerBase(object): pass

class pluginA(PluginBase): "Now it is clearly a plugin class"
class pluginB(PluginBase): "Another plugin"

class Server1(ServerBase, pluginA, pluginB): "This works"
class Server2(ServerBase): pass
Server2.__base__ += (PluginA,) # This also works

注意:可能你不需要工厂;它在C ++中是必需的,但在Python中几乎没有。