“超级”在 Python 中做什么? - super().__init__() 和显式超类 __init__() 之间的区别

新手上路,请多包涵

有什么区别:

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

和:

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

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

原文由 user25785 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 272
2 个回答

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

但是,如果没有 super() 几乎不可能使用多重继承。这包括常见的习语,如混合、接口、抽象类等。这扩展到稍后扩展你的代码。如果以后有人想写一个扩展的类 Child 和一个 mixin,他们的代码将无法正常工作。

原文由 John Millikin 发布,翻译遵循 CC BY-SA 2.5 许可协议

有什么不同?

 SomeBaseClass.__init__(self)

意味着调用 SomeBaseClass__init__ 。尽管

super().__init__()

意味着调用一个绑定 __init__ 从父类跟随 SomeBaseClass 在实例的方法解析顺序(MRO)中的子类(定义此方法的那个)。

如果该实例是 子类的子类,则 MRO 中下一个可能有不同的父类。

简单解释

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

正如 Bob Martin 所说,一个好的架构可以让你尽可能地推迟决策制定。

super() 可以启用那种架构。

当另一个类继承您编写的类时,它也可以从其他类继承。这些类可能有一个 __init__ 在此之后 __init__ 基于方法解析类的顺序。

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

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

Python 2 与 3

这适用于 Python 2 和 3:

 super(Child, self).__init__()

这仅适用于 Python 3:

 super().__init__()

它通过在堆栈帧中向上移动并获取方法的第一个参数(通常 self 对于实例方法或 cls 对于类方法 - 但可以是其他名称)并在自由变量中查找类(例如 Child )(使用名称 __class__ 作为方法中的自由闭包变量进行查找)。

我以前更喜欢演示使用 super 的交叉兼容方式,但现在 Python 2 已基本弃用,我将演示 Python 3 的处理方式,即调用 super 没有争论。

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

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

前向兼容性对于经验丰富的开发人员来说非常重要。您希望您的代码在更改时保持最小的更改。当您查看修订历史记录时,您希望准确了解什么时候发生了什么变化。

您可以从单一继承开始,但如果您决定添加另一个基类,则只需更改与基类的行 - 如果基类在您继承自的类中发生变化(比如添加了混合),您将更改这堂课什么都没有。

在 Python 2 中,获取 super 的参数和正确的方法参数可能有点混乱,所以我建议使用 Python 3 only 调用它的方法。

如果您知道您正在使用 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().__init__()

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

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

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

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

 >>> 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

发表评论

为什么这在世界上会有用?

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

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

对于在父级中定义的方法以在没有 super 的情况下找到该顺序中的下一个,它必须

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

UnsuperChild 不应访问 InjectMe 。为什么不是“始终避免使用 super ”的结论?我在这里错过了什么?

UnsuperChild 无权 访问 InjectMe 。它是 UnsuperInjector 有权访问 InjectMe 但不能从它继承自 UnsuperChild 的方法调用该类的方法

两个 Child 类都打算调用 MRO 中下一个同名的方法,这可能是它在创建时不知道的 另一个 类。

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

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

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

结论

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

您打算引用下一个父类,而不是您看到的子类继承自的具体父类。

不使用 super 会对代码的用户施加不必要的限制。

原文由 Russia Must Remove Putin 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题