【问题标题】:How to invoke the constructor of every parent exactly once?如何只调用每个父级的构造函数一次?
【发布时间】:2019-07-27 12:51:41
【问题描述】:

假设有一个简单的多重继承设置,有两个基类AB 和一个子类C 从两者继承。

class A:
    def __init__(self):
        print("Started A's constructor")
        # -- Not calling: super().__init__() --
        print("Ended A's constructor")

class B:
    def __init__(self):
        print("Started B's constructor")
        super().__init__()
        print("Ended B's constructor")

class C(A, B):
    def __init__(self):
        print("Started C's constructor")
        super().__init__()
        print("Ended C's constructor")

接到电话

c = C()

我们得到输出

Started C's constructor
Started A's constructor
Ended A's constructor
Ended C's constructor

如果我想为每个C 对象调用两个基类构造函数,并且无权访问基类来添加super().__init__() 调用,应该更改或添加到C 什么?有没有办法做到这一点,如果A 确实调用super().__init__(),它不会中断? (编辑:要清楚,将super().__init__() 添加到A 将调用B 的构造函数,因为它是MRO 中的下一个;为了更清楚:code example

更重要的是,在一般情况下,如果想对每个祖先的每个__init__ 方法只调用一次,并且不确定每个__init__ 函数是否调用super().__init__(),那么如何确保调用每个父类一次且只有一次?

如果这不可能,这不会破坏基本的面向对象原则吗?简单来说,在这种情况下,A 的实现不应该影响B 的行为。

【问题讨论】:

  • 你不应该在B.__init__ 中调用super。您只需拨打__init__object 即可。
  • 如果开发人员不打算让人们调用它,为什么不选择 object__init__ 不做任何事情?您是否有任何参考或进一步了解为什么永远不应该调用它,并且不会在任何 python 库中调用它?这样的答案可能比评论更适合作为答案。
  • 要使用协作多重继承和super,那么每个类都需要调用super。拨打super().__init__()A.__init__
  • @PaulRooney 不,它不是这样工作的。您需要在 BA 中调用 super 以获取完整的 MRO 链。 super 并不意味着调用直接父类。这意味着在 MRO 中调用下一个。如果您的类要使用协作多重继承,则需要调用super
  • @PaulRooney:当你构造一个B时,超级调用只是调用object.__init__。在多重继承的情况下,这就是这个问题的重点,超级调用可能会调用不同的__init__而不是object.__init__

标签: python python-3.x oop multiple-inheritance


【解决方案1】:

A没有使用super,所以不能用于合作继承。相反,根据Python's super() considered super!,为A 定义一个包装器。 (假设你不能简单地修复A。)

class A:
    def __init__(self):
        print("Started A's constructor")
        print("Ended A's constructor")


# AWrapper also needs to override each method defined by A;
# those simply delegate their work to the intenral instance of A
# For example,
#
#  def some_method(self, x, y):
#      return self.a.some_method(x, y)
#
class AWrapper:
    def __init__(self, **kwargs):
        self.a = A()
        super().__init__(**kwargs)


class B:
    ...


class C(AWrapper, B):
    ...

【讨论】:

  • @Aran-Fey:这将 A 置于 MRO 中,现在您又回到了处理 A 的 __init__ 而不使用 super
  • @Aran-Fey:你打算如何修复 A 的 __init__?覆盖它并不能取代它。它仍然在 MRO 中,它仍然会打破 super 链。
  • @Aran-Fey 这通常不起作用,因为super 不一定会返回第一个静态定义的基类的代理;它完全取决于 self 的 MRO,它可能是您甚至还不知道的类的一个实例。
  • 问题是这显然是一个玩具示例,我不打算支持仅适用于这个玩具示例的解决方案。如果AB 有一个共同的祖先,并且A 显式调用它的__init__ 会怎样。该方法最终可能会被调用两次。 super 的重点是确保每个方法都被调用一次,按照它们在 MRO 中出现的顺序。如果您要使用super,请正确使用,这意味着不要硬编码对特定基类方法的调用。
  • 但是如果A 显式调用object.__init__,如何避免object.__init__ 被调用两次?仅仅因为您的 AWrapper 使用组合而不是继承并不意味着 object.__init__ 只会被调用一次。它会从A.__init__ 调用一次,从B.__init__ 调用一次。我看不出组合在这里比继承有什么优势。
【解决方案2】:

可以遍历类对象的__bases__属性来调用每个父类的__init__方法:

class C(A, B):
    def __init__(self):
        print("Started C's constructor")
        for base in self.__class__.__bases__:
            base.__init__(self)
        print("Ended C's constructor")

所以C() 输出:

Started C's constructor
Started A's constructor
Ended A's constructor
Started B's constructor
Ended B's constructor
Ended C's constructor

【讨论】:

  • 我考虑过这个,但是如果A 调用super().__init__() 这将不起作用,因为B 的构造函数将被调用两次。尽管我想关注的一般情况涵盖了这种可能性,但我会更清楚地说明问题可能是这种情况。
  • 你的意思是AB的父类吗?如果是这样,您应该用class B(A): 定义B
  • 一点也不。如果将调用添加到A 的构造函数,它将调用B 的构造函数,因为它在MRO 中是下一个。我试图在这里导航的正是这种违反直觉的行为。
  • @blhsing 不,一点也不。 super 不是这样工作的。 super 调用mro 中的下一项,而不是超类。最简单的解决方案是让AA.__init__ 中调用super().__init__,然后这一切都会如您所愿。
  • @blhsing 也许这样更清楚:repl.it/repls/MatureCaringDirectories
猜你喜欢
  • 2016-12-21
  • 2010-12-06
  • 1970-01-01
  • 2017-09-28
  • 1970-01-01
  • 2017-07-18
  • 2011-09-30
  • 2017-08-29
  • 1970-01-01
相关资源
最近更新 更多