6

请考虑以下代码实现一个简单的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都源自object,于是钻石问题就出现了。

解决方案是简单地制作StoryHTMLMixin一个旧式类,即删除继承自object,因此,将类的定义更改StoryHTMLMixin为:

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

运行时导致以下结果main

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

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

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

编辑:

我看到最新的 Python 源代码UserDict中的类定义了一个使用旧样式类的 MixIn(如我的示例中所示)。

正如所有人的建议,我可能会在不使用 MixIns 的情况下重新定义我想要获得的功能(即在运行时绑定方法)。然而,重点仍然存在——这是唯一一个在不求助于重新实现或回退到旧式类的情况下无法解决 MRO 的用例吗?

4

6 回答 6

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()

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

于 2010-12-23T00:17:53.847 回答
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从基础中删除,但由于有些不透明的内部原因( ),这是不允许的。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()
于 2010-12-23T00:27:18.087 回答
2

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

class Text(object): pass
class Story(Text):
    ....

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

我个人喜欢类装饰器。

于 2010-12-23T00:17:22.847 回答
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>
于 2010-12-23T02:17:06.943 回答
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):
  ...
于 2010-12-23T00:05:31.870 回答
1

除了前面的答案所说的一切(以及邪恶和坏主意)之外,您的问题是:

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

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

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

    class MyBase(object): pass
    
    
    class Story(MyBase):
         ...
    
    
    class StoryHTMLMixin(MyBase):
         ...
    
于 2010-12-23T08:42:31.730 回答