在Python中使用MixIns时出现钻石问题

时间:2010-12-22 23:55:00

标签: python multiple-inheritance mixins diamond-problem

请考虑以下代码实现简单的MixIn

class Story(object):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(object):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

def MixIn(TargetClass, MixInClass):
    if MixInClass not in TargetClass.__bases__:
     TargetClass.__bases__ += (MixInClass,)

if __name__ == "__main__":
    my_story = Story("My Life", "<p>Is good.</p>")
    # plug-in the MixIn here
    MixIn(Story, StoryHTMLMixin)
    # now I can render the story as HTML
    print my_story.render()

运行main会导致以下错误:

TypeError: Cannot create a consistent method resolution
order (MRO) for bases object, StoryHTMLMixin

问题在于StoryStoryHTMLMixin都来自objectdiamond problem出现了。

解决方案只是使StoryHTMLMixin成为旧式类,即从object中删除继承,从而更改类{{1}的定义转到:

StoryHTMLMixin
运行class StoryHTMLMixin: def render(self): return ("<html><title>%s</title>" "<body>%s</body></html>" % (self.name, self.content))

会导致以下结果:

main

我不喜欢使用旧样式类,所以我的问题是:

这是在Python中处理此问题的正确方法,还是有更好的方法?

编辑:

我看到latest Python source中的类<html><title>My Life</title><body><p>Is good.</p></body></html> 定义了一个求助于旧样式类的MixIn(如我的示例所示)。

根据所有人的建议,我可以在不使用MixIns的情况下重新定义我想要实现的功能(即,在运行时绑定方法)。但是,这一点仍然存在 - 这是唯一一个在没有重新实现或退回旧式课程的情况下弄乱MRO无法解决的用例吗?

6 个答案:

答案 0 :(得分:5)

为什么不直接使用mixins而不是在mro中乱砍?

class Story(object):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(object):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

class StoryHTML(Story, StoryHTMLMixin):
    pass


print StoryHTML('asd','asdf').render() # works just fine

如果你真的,真的,真的想要在课堂上粘贴额外的方法,这不是一个大问题。好吧,除了它是邪恶的和坏的做法。无论如何,您可以随时更改课程:

# first, the story
x = Story('asd','asdf')

# changes a class object
def stick_methods_to_class( cls, *methods):
    for m in methods:
        setattr(cls, m.__name__, m)

# a bare method to glue on
def render(self):
 return ("<html><title>%s</title>"
     "<body>%s</body></html>"
     % (self.name, self.content))

# change the class object
stick_methods_to_class(Story, render)

print x.render()

但最后,问题仍然存在:为什么班级会突然增加额外的方法?这就是恐怖电影的组成部分; - )

答案 1 :(得分:4)

如果我们消除__bases__魔法并写下您明确创建的类,则更容易看到发生了什么:

class StoryHTMLMixin(object):
    def render(self):
        return ("<html><title>%s</title>"
            "<body>%s</body></html>"
            % (self.name, self.content))

class Story(object, StoryHTMLMixin):
    def __init__(self, name, content):
        self.name = name
        self.content = content

这是你正在做的最终结果 - 或者如果成功的话会是什么样的结果。它会导致相同的错误。

请注意,这实际上不是钻石继承。这涉及四个类,其中两个基类各自继承一个共同的第四类; Python的多重继承涉及到它。

这里只有三个类,导致继承看起来像这样:

StoryHTMLMixin <--- Story
            |   _____/
            |  |
            v  v
           object

Python不知道如何解决这个问题。

我不知道有关此方法的解决方法。原则上,解决方案是在向object添加Story的同时从StoryHTMLMixin的基础中移除TypeError: __bases__ assignment: 'StoryHTMLMixin' deallocator differs from 'object',但由于某些不透明的内部原因({} {1}})。

我从来没有发现任何实际的,真实世界的用于修改这样的类。它似乎是混淆和混乱 - 如果你想要一个派生自这两个类的类,只需正常创建一个类。

版:

这是一种类似于你的方法,但不需要就地修改类。注意它是如何返回一个新类,从函数的参数动态导出。这更清楚 - 例如,您无法无意中修改已实例化的对象。

class Story(object):
    def __init__(self, name, content):
        self.name = name
        self.content = content

class StoryHTMLMixin(object):
    def render(self):
        return ("<html><title>%s</title>"
            "<body>%s</body></html>"
            % (self.name, self.content))

def MixIn(TargetClass, MixInClass, name=None):
    if name is None:
        name = "mixed_%s_with_%s" % (TargetClass.__name__, MixInClass.__name__)

    class CombinedClass(TargetClass, MixInClass):
        pass

    CombinedClass.__name__ = name
    return CombinedClass

if __name__ == "__main__":
    MixedStory = MixIn(Story, StoryHTMLMixin, "MixedStory")
    my_story = MixedStory("My Life", "<p>Is good.</p>")
    print my_story.render()

答案 2 :(得分:2)

编辑:我的不好,这个错误是一个单独的问题(感谢@Glenn Maynard)。只要您的mixin直接继承自object

,以下hack仍然有效
class Text(object): pass
class Story(Text):
    ....

但是,我认为mixins不是解决问题的最佳方法。提供的其他解决方案(类装饰器和普通子类)清楚地将Story类标记为可渲染,而您的解决方案隐藏了这一事实。也就是说,其他解决方案中render方法的存在是显式,而在您的解决方案中它是隐藏的。我认为这会引起混乱,特别是如果你更多地依赖mixin方法。

我个人喜欢类装饰器。

答案 3 :(得分:2)

  

然而,重点仍然是 - 是   这是弄乱的唯一用例   没有MRO是无法解决的   诉诸重新实施或   回到旧式的课程?

其他人提出了更好的解决方案 - 例如明确构建 期望的课程 - 但要回答您编辑的问题,可能来定义 mixin没有诉诸旧式课程:

class Base(object): pass

class Story(Base):
    def __init__(self, name, content):  
     self.name = name
     self.content = content    

class StoryHTMLMixin(Base):
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))

def MixIn(TargetClass, MixInClass):
    if MixInClass not in TargetClass.__bases__:
        TargetClass.__bases__ = (MixInClass,)+TargetClass.__bases__

if __name__ == "__main__":
    my_story = Story("My Life", "<p>Is good.</p>")
    # plug-in the MixIn here
    MixIn(Story, StoryHTMLMixin)
    # now I can render the story as HTML
    print my_story.render()

产量

<html><title>My Life</title><body><p>Is good.</p></body></html>

答案 4 :(得分:1)

您可以尝试使用装饰器来添加功能:

def render(obj):
  return ("<html><title>%s</title>"
    "<body>%s</body></html>"
    % (obj.name, obj.content))

def renderable(cls):
  cls.render = render
  return cls

@renderable
class Story(object):
  ...

答案 5 :(得分:1)

除了以前的答案(并且是邪恶和坏主意)所说的一切,你的问题是:

  1. 基地应该以相反的方式订购

    TargetClass.__bases__ = (MixInClass,) + TargetClass.__bases__

  2. http://bugs.python.org/issue672115 - 最简单的解决方法是从用户定义的类而不是对象继承所有内容,例如

    class MyBase(object): pass
    
    
    class Story(MyBase):
         ...
    
    
    class StoryHTMLMixin(MyBase):
         ...