请考虑以下代码实现简单的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
问题在于Story
和StoryHTMLMixin
都来自object
,diamond 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无法解决的用例吗?
答案 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)
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)
除了以前的答案(并且是邪恶和坏主意)所说的一切,你的问题是:
基地应该以相反的方式订购
TargetClass.__bases__ = (MixInClass,) + TargetClass.__bases__
http://bugs.python.org/issue672115 - 最简单的解决方法是从用户定义的类而不是对象继承所有内容,例如
class MyBase(object): pass
class Story(MyBase):
...
class StoryHTMLMixin(MyBase):
...