“super”在Python中做什么?

2024-04-19 21:41:03 发布

您现在位置:Python中文网/ 问答频道 /正文

有什么区别:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

以及:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

我已经看到super在只有单一继承的类中被大量使用。我可以理解为什么要在多重继承中使用它,但不清楚在这种情况下使用它有什么好处。


Tags: selfchildinitdef情况classsuper区别
3条回答

在单继承中,super()的好处是最小的——大多数情况下,不必将基类的名称硬编码到使用其父方法的每个方法中。

然而,如果没有super(),几乎不可能使用多重继承。这包括诸如mixin、接口、抽象类等常见的习惯用法。这扩展到以后扩展您的代码。如果后来有人想编写一个扩展了Child和mixin的类,那么他们的代码将无法正常工作。

所有这些不都假设基类是一个新样式的类吗?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

在Python 2中不起作用。class A必须是新样式,即:class A(object)

有什么区别?

SomeBaseClass.__init__(self) 

意思是调用SomeBaseClass__init__。当

super(Child, self).__init__()

意味着从实例方法解析顺序(MRO)中Child后面的父类调用绑定__init__

如果实例是子类的子类,那么MRO中可能会出现另一个父类。

简单解释

当你写一个类时,你希望其他类能够使用它。super()使其他类更容易使用您正在编写的类。

正如Bob Martin所说,一个好的架构允许您尽可能长时间地推迟决策。

super()可以启用这种体系结构。

当另一个类对您编写的类进行子类化时,它也可能是从其他类继承的。并且这些类可以有一个__init__,这个__init__后面是基于方法解析类的顺序的。

如果没有super,您可能会硬编码正在编写的类的父类(如示例所示)。这意味着您不会调用MRO中的下一个__init__,因此您将无法重用其中的代码。

如果您是为个人使用而编写自己的代码,您可能不关心这种区别。但是如果您希望其他人使用您的代码,那么使用super是一件可以为代码用户提供更大灵活性的事情。

Python 2对3

这在Python2和3中有效:

super(Child, self).__init__()

这只适用于Python 3:

super().__init__()

它在堆栈框架中向上移动并获取方法的第一个参数(通常是实例方法的self,或者是类方法的cls,但可以是其他名称),然后在自由变量中查找类(例如Child)(在方法)。

我更喜欢演示使用super的交叉兼容方式,但是如果您只使用Python 3,则可以不带参数地调用它。

具有前向兼容性的间接寻址

它给了你什么?对于单继承,从静态分析的角度来看,问题中的示例实际上是相同的。但是,使用super可以提供一个具有前向兼容性的间接层。

前向兼容性对于经验丰富的开发人员非常重要。您希望您的代码在更改时保持最小的更改。当你查看你的修订历史时,你想知道什么时候改变了。

你可以从单继承开始,但是如果你决定添加另一个基类,你只需要改变带基类的行-如果基类在你继承的类中改变(比如说添加了mixin),你在这个类中什么也不会改变。特别是在Python 2中,要获得super的参数和正确的方法参数可能很困难。如果您知道您对单个继承正确地使用了super,那么接下来的调试就不那么困难了。

依赖注入

其他人可以使用您的代码并将父类注入到方法解析中:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

假设您将另一个类添加到对象中,并希望在Foo和Bar之间插入一个类(出于测试或其他原因):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

使用un super子项无法插入依赖项,因为您正在使用的子项已硬编码要在其自身之后调用的方法:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

但是,具有使用super的子类的类可以正确地注入依赖项:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

发表评论

Why in the world would this be useful?

Python通过C3 linearization algorithm将复杂的继承树线性化,以创建方法解析顺序(MRO)。

我们希望按顺序查找方法。

对于在父级中定义的方法,若要在不使用super的情况下按该顺序查找下一个方法,则必须

  1. 从实例的类型中获取mro
  2. 看for定义方法的类型
  3. 使用方法查找下一个类型
  4. 绑定该方法并用期望的参数调用它

The UnsuperChild should not have access to InjectMe. Why isn't the conclusion "Always avoid using super"? What am I missing here?

UnsuperChild没有访问InjectMe的权限。有权访问InjectMe的是UnsuperInjector,但无法从继承自UnsuperChild的方法调用该类的方法。

两个子类都打算用MRO中接下来出现的相同名称调用一个方法,这可能是另一个在创建时不知道的类。

没有super硬编码的方法是其父方法,因此限制了其方法的行为,子类不能在调用链中注入功能。

带有super具有更大的灵活性。方法的调用链可以被拦截并注入功能。

您可能不需要该功能,但代码的子类可能需要。

结论

始终使用super来引用父类,而不是对其进行硬编码。

您想要引用的是下一行的父类,而不是您看到的子类继承自的父类。

不使用super会给代码的用户带来不必要的限制。

相关问题 更多 >