10

假设我有一堂课AB并且C

ClassAB都是 Class 的 mixin 类C

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

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

TypeError:调用元类基础时出错
无法
为基础 B、对象、A 创建一致的方法解析顺序 (MRO)

但是,我的情况有点复杂。在我的例子中,类C是一个服务器,其中A并且B将是在启动时加载的插件。这些都驻留在自己的文件夹中。

我还有一个名为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:无法
为基础对象 A 创建一致的方法解析顺序 (MRO)

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

另一个简单的解决方法是从中删除objectC但这也会使其成为旧式类并且C.__bases__将不可用,因此我无法将额外的对象添加到C

什么是一个好的架构解决方案,你将如何做这样的事情?现在我可以忍受插件本身的旧式类。但我宁愿不使用它们。

4

3 回答 3

11

可以这样想——您希望 mixins 覆盖 的某些行为object,因此它们需要object在方法解析顺序中位于之前。

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

class C(A, B, object):
    pass

由于这个错误,您不需要C直接从 object 继承才能正确分配给__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()
于 2012-04-12T22:26:13.083 回答
4

我不知道细节,所以也许我在这里完全偏离了基础,但似乎您使用错误的机制来实现您的设计。

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

老实说,C在我看来也不太像一门课。似乎您不能创建一个以上的实例,否则您最终会得到一个不断增长的基础列表。所以这C基本上是一个模块而不是一个类,只有额外的样板。我尽量避免使用“单实例类来表示应用程序或某些外部系统”模式(尽管我知道它很流行,因为 Java要求您使用它)。但是类继承机制对于不是真正类的东西通常很方便,比如你的插件系统。

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

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

实际上,如果类不能在创建后立即进入一致状态,我可能宁愿创建动态类而不是修改现有类的基础。这样系统就不会被锁定在一次配置和一次实例化的类中;您至少对加载不同插件集的多个实例的可能性持开放态度。像这样的东西:

def Cfactory(*args, **kwargs):
    plugins = find_plugins()
    bases = (C,) + plugins
    cls = type('C_with_plugins', bases, {})
    return cls(*args, **kwargs)

这样,您只需一次调用即可创建您的C实例,并为您提供正确配置的实例,但它不会对可能已经存在的任何其他假设实例产生奇怪的副作用C,并且它的行为不取决于它是否之前运行过。我知道您可能不需要这两个属性中的任何一个,但它的代码几乎不比简化示例中的代码多,如果不需要,为什么要打破类的概念模型?

于 2012-04-12T23:27:10.290 回答
3

有一个简单的解决方法:创建一个助手类,具有一个好听的名称,例如PluginBase。并使用它的继承,而不是对象。

这使代码更具可读性(恕我直言),并且解决了该错误。

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 中几乎不需要

于 2012-11-09T16:48:30.380 回答