Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Diamond problem when using MixIns in Python

Please consider the following code implementing a simple 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()

Running main leads to the following error:

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

The problem is that both Story and StoryHTMLMixin are derived from object, and the diamond problem arises.

The solution is simply to make StoryHTMLMixin an old-style class, i.e., remove the inheritance from object, thus, changing the definition of the class StoryHTMLMixin to:

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

leads to the following result when running main:

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

I don't like having to use old style classes, so my question is:

Is this the correct way to handle this problem in Python, or is there a better way?

Edit:

I see that the class UserDict in the latest Python source defines a MixIn resorting to the old style classes (as presented in my example).

As recommended by all, I may resort to redefining the functionality that I want to attain (namely, the binding of methods at run time) without using MixIns. However, the point still remains - is this the only use case where messing with the MRO is unsolvable without resorting to reimplementation or falling back to old-style classes?

like image 252
Escualo Avatar asked Dec 22 '10 23:12

Escualo


1 Answers

Why don't you use mixins directly instead of hacking around in the 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

If you really, really, really want to glue extra methods on a class, that is not a big problem. Well, except that it's evil and bad practice. Anyways, you can change a class at any time:

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

But in the end, the question remains: Why should classes suddenly grow extra methods? That's the stuff horror movies are made of ;-)

like image 104
Jochen Ritzel Avatar answered Sep 25 '22 14:09

Jochen Ritzel