super()类继承,钻石问题

3
为什么在类WordCounter中调用super()时会调用类Vocabulary,即使它没有继承自该类?难道不应该调用类Tokenizer吗?
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')

输出:

Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
1个回答

3

钻石继承总是有点混乱。每种语言都有自己的怪癖,Python也不例外。(请注意,我在解释Python中的“新式”类,Python 2中还有“经典”类,行为不同)

Python对于所有继承做的事情是为其规定一个检查父类顺序的方法,如果当前类没有实现请求的方法/属性,就按照这个顺序检查。您可以动态地检查此方法解析顺序。您的示例产生以下结果:

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

如您所见,Python选择从左到右,然后再下降到层次结构中(详细信息)。

super() 的作用是调用该 __mro__ 中的下一个方法。由于 Vocabulary.__init__() 方法没有调用 super().__init__() 来继续执行链条,因此该链在该方法处停止。

如果在 Vocabulary.__init__() 中包含 super().__init__() 调用,则可以正常工作:

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

谢谢@Niobos为我解释MRO,但这并没有告诉我它为什么选择Vocabulary而不是Tokenizer?有什么逻辑背后吗? - Kashan
这只是Python定义继承顺序的方式。如果您想了解所有边缘情况的更多信息,我已经添加了完整规范的链接。 - Niobos
1
@Kashan。请记住,在WordCounter.__init__中调用super().__init__(text)等同于super(WordCounter, self).__init__(text)。这两个参数的意思是“使用self的mro,从WordCounter开始”。我们习惯于看到self的类型为WordCounter或单一继承自它。当不是这种情况时,WordCounter的父类可能不是mro中的下一个元素,就像你在这里看到的那样。这就是答案中“super()的作用是调用此__mro__中的下一个方法”的句子的含义。 - Mad Physicist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接