Python - 调用父类方法的更好方式是什么?

17 投票
3 回答
13725 浏览
提问于 2025-04-16 07:45

我一直在使用:

SuperClass.__init__(self, *args, **kwargs)

我这么做的原因是,这样可以清楚地显示出使用了哪个父类,特别是在多重继承的情况下。

不过,我看到的其他代码使用的是

super(MyClass, self).__init__(*args, **kwargs)

而不是这个。

当它在以下情况下使用时,这可能会变得模糊:

class MyClass(SuperClass1, SuperClass2):
    def __init__(self, *args, **kwargs):
        super(MyClass, self).__init__(*args, **kwargs) #which SuperClass is being used?

我想知道为什么这种调用方式被广泛采用?有什么好处吗?

3 个回答

3

除了已经发布的好答案,这里还有一些额外的信息:

  • 旧式类(那些不继承自对象的类)采用的是深度优先的方法解析顺序。旧式类调用它们的父类的方式是你熟悉的那种。

  • 新式类(那些继承自对象的类)的方法解析顺序更复杂。它在最高层面上类似于广度优先,但比这更复杂。想了解更多,可以查看这个页面,里面有很好的解释。使用“super()”可以让新式类遵循这种方法解析顺序。

  • 如果你在使用新式类,仍然可以用旧式的方式调用父类,你的代码依然可以正常工作。只有在更复杂的多重继承模式下,差异才会变得明显。

6

对于新式类,也就是那些继承自 object 的类,我们使用 super 来处理。

__mro__(方法解析顺序)这个属性会列出 super 查找方法的顺序。

>>> class X(object):
    pass

>>> class Y(object):
    pass

>>> class Z(X, Y):
    pass

>>> Z.__mro__
(<class '__main__.Z'>, <class '__main__.X'>, <class '__main__.Y'>, <type 'object'>)

这里指定了 Z 的顺序。因为它是 Z(X, Y),所以 X 在继承层次中排第一。如果是 Z(Y, X),那么 Y 就会排在 X 前面。

对于旧式类,我们使用 SuperClass.__init__(self, *args, **kwargs)

更新:

关于你问的使用哪个 SuperClass

>>> class First(object):
    pass

>>> class Second(object):
    def __init__(self, *args, **kwargs):
        print 'Second __init__ called'

>>> class MInherit(First, Second):
    def __init__(self):
        super(MInherit, self).__init__()

>>> i = MInherit()
Second __init__ called

首先会检查 First 是否有 __init__ 方法,因为 First 在方法解析顺序中排第一。如果 First 没有 __init__,那么就会调用 Second。如果 First__init__,那么只会调用它。

21

使用super在现代(新风格)类中更受欢迎的原因是,它支持合作式多重继承。让我给你举个例子。

>>> class Foo(object):
...     def display(self):
...         print "In Foo"
... 
>>> class Foo2(Foo):
...     def display(self):
...         print "In Foo2"
...         super(Foo2, self).display()
...         print "Back in Foo2"
... 
>>> class Bar(Foo):
...     def display(self):
...         print "In Bar"
...         super(Bar, self).display()
...         print "Back in Bar"
... 
>>> class FooBar(Foo2, Bar):
...     pass
... 
>>> FooBar().display()
In Foo2
In Bar
In Foo
Back in Bar
Back in Foo2
>>> class BarFoo(Bar, Foo2):
...     pass
... 
>>> BarFoo().display()
In Bar
In Foo2
In Foo
Back in Foo2
Back in Bar

注意,我并没有对父类的display方法做任何修改,但通过改变父类的排列顺序,我在子类中得到了不同的display方法。BarFooFooBar有不同的方法。这是因为它们有不同的方法解析顺序

>>> BarFoo.__mro__
(<class '__main__.BarFoo'>, <class '__main__.Bar'>, <class '__main__.Foo2'>, <class '__main__.Foo'>, <type 'object'>)
>>> FooBar.__mro__
(<class '__main__.FooBar'>, <class '__main__.Foo2'>, <class '__main__.Bar'>, <class '__main__.Foo'>, <type 'object'>)

这意味着在每个子类中调用super时,它会解析到不同的父类。这让每个重写的方法可以改变一小部分内容,同时仍然让其他父类参与到方法调用中,只要它们愿意和谐合作。

撰写回答