super() class inheritance, Diamond Problem

Viewed 74

Why when I call super() in the class WordCounter it calls the class Vocabulary even though it's not inheriting from? Shouldn't it call class Tokenizer instead?

class Tokenizer:
    """Tokenize text"""
    def __init__(self, text):
        print('Start Tokenizer.__init__()')
        print('End Tokenizer.__init__()')


class Vocabulary(Tokenizer):
    """Find unique words in text"""
    def __init__(self, text):
        print('Start init Vocabulary.__init__()')
        print('End init Vocabulary.__init__()')


class WordCounter(Tokenizer):
    """Count words in text"""
    def __init__(self, text):
        print('Start WordCounter.__init__()')
        super().__init__(text)
        print('End WordCounter.__init__()')


class TextDescriber(WordCounter, Vocabulary):
    """Describe text with multiple metrics"""
    def __init__(self, text):
        print('Start init TextDescriber.__init__()')
        super().__init__(text)
        print('End init TextDescriber.__init__()')


td = TextDescriber('row row row your boat')

OUTPUT:

Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
1 Answers

Diamond inheritance is always a bit of a mess. Every language has its own quirks, and Python is no exception. (Note that I'm explaining "new style" classes in Python. There are also "classic" classes in Python 2, which behave differently)

What Python does for all inheritance, is to render an order in which to check parent classes if the current class does not implement the requested method/property. You can inspect this Method Resolution Order dynamically. Your example yields

>>> print(TextDescriber.__mro__)
(<class '__main__.TextDescriber'>, <class '__main__.WordCounter'>, <class '__main__.Vocabulary'>, <class '__main__.Tokenizer'>, <class 'object'>)

As you can see, Python chose to go left-to-right, and only afterwards descend in the hierarchy (full details).

What super() does is call the next method in this __mro__. This chain stops at Vocabulary.__init__(), since this method has no super().__init__() call for the chain to continue.

If you include a super().__init__() call in Vocabulary.__init__(), this will work as expected:

Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
Related