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

Esc*_*alo 6 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()
Run Code Online (Sandbox Code Playgroud)

运行main会导致以下错误:

TypeError: Cannot create a consistent method resolution
order (MRO) for bases object, StoryHTMLMixin
Run Code Online (Sandbox Code Playgroud)

问题是,无论StoryStoryHTMLMixin衍生自object钻石的问题就出现了.

解决方案只是创建StoryHTMLMixin一个旧式类,即删除继承object,从而将类的定义更改StoryHTMLMixin为:

class StoryHTMLMixin:
    def render(self):
     return ("<html><title>%s</title>"
         "<body>%s</body></html>"
         % (self.name, self.content))
Run Code Online (Sandbox Code Playgroud)

运行时会导致以下结果main:

<html><title>My Life</title><body><p>Is good.</p></body></html>
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:

我看到类UserDict最新的Python源定义一个mixin诉诸老样式类(如在我的例子给出).

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

Joc*_*zel 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
Run Code Online (Sandbox Code Playgroud)

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

# 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()
Run Code Online (Sandbox Code Playgroud)

但最终,问题仍然存在:为什么课程会突然增加额外的方法?这就是恐怖片制作的东西;-)


Gle*_*ard 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
Run Code Online (Sandbox Code Playgroud)

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

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

这里只有三个类,导致继承如下所示:

StoryHTMLMixin <--- Story
            |   _____/
            |  |
            v  v
           object
Run Code Online (Sandbox Code Playgroud)

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

我不知道这个问题的解决方法。原则上,解决方案是在添加的同时object从基础中删除,但由于有些不透明的内部原因,这是不允许的()。StoryStoryHTMLMixinTypeError: __bases__ assignment: 'StoryHTMLMixin' deallocator differs from 'object'

无论如何,我从未发现任何实际的、现实世界的用途来修改这样的类。它看起来很混乱——如果您想要一个从这两个类派生的类,只需正常创建一个类即可。

埃德:

这是一种与您的方法类似的方法,但无需就地修改类。请注意它如何返回一个新类,从函数的参数动态派生。这更加清楚——例如,您不能无意中修改已经实例化的对象。

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()
Run Code Online (Sandbox Code Playgroud)

  • 撇开最初的问题不谈,这似乎是一个有用的例子,可以证明类如何像其他东西一样是简单的对象;需要一定的语言舒适度才能意识到这是可能的。 (2认同)