具有不同初始化参数的多重继承的真正解决方案

The real solution for multiple inheritance with different init parameters

这看起来是个简单的任务,但我现在在这上面花了太多时间,却没有解决方案。这是设置:

class A(object):
  def __init__(self, x=0):
    print("A.__init__(x=%d)" % x)

class B(object):
  def __init__(self, y=1):
    print("B.__init__(y=%d)" % y)

class C(A, B):
  def __init__(self, x=2, y=3, z=4):
    super().__init__(x=x, y=y)
    print("C.__init__(z=%d)" % z)

这就是想法,但当然这会导致

TypeError: __init__() got an unexpected keyword argument 'y'

所有其他尝试都以类似方式失败,我可以在 Internet 上找到 no 资源和正确的解决方案。唯一的解决方案包括用 *args, **kwargs 替换所有初始化参数。这不太符合我的需求。


根据要求,一个真实世界的例子:
(这使用了不同的方法,它具有有效的语法,但会产生不需要的结果。)

from PyQt5.QtCore import QObject

class Settings(object):
    def __init__(self, file):
        self.file = file

class SettingsObject(object):
    def __init__(self, settings=None):
        print("Super Init", settings is None)
        self.settings = settings

class MyObject(QObject, SettingsObject):
    def __init__(self, param):
        print("Entering Init")
        QObject.__init__(self)
        SettingsObject.__init__(self, settings=Settings(__file__))
        self.param = param
        print("Leaving Init")

结果:

Entering Init
Super Init True
Super Init False
Leaving Init

我希望 Super Init True 行消失。

您似乎对一些事情感到困惑,所以:

  1. 为什么会得到TypeError: __init__() got an unexpected keyword argument 'y'?

    因为方法解析顺序(MRO)中__init__的下一个实现是A.__init__,它只接受x。 MRO 是 C -> A -> B,因此你必须让 A.__init__ 接受 y(具体或使用 **kwargs)并且将其传递(再次使用 super)给 B.__init__

    super 不会调用 every 实现,当然也不能确保他们都只得到他们期望的参数,它只是调用 next 实现所有给定的参数。

  2. 为什么 SettingsObject.__init__ 被调用两次,一次有 settings,一次没有 settings

    QObject.__init__ 似乎包含对 super().__init__ 的调用,它会调用 SettingsObject.__init__,因为它是 MyObject 的 MRO 中的下一个。但是它不传递任何 settings 参数,因此第一次调用它时您会看到 settings is None。第二次直接调用它,并显式传递 settings,所以你会看到 settings is not None.

  3. 你怎么写mix-in classes?

    我认为这是你真正应该问的问题。 SettingsObject 应该是 mix-in class,因此设计正确,可以与它所混入的层次结构中的其他 classes 协作。在这种情况下:

    class SettingsObject:  # (object) isn't needed in 3.x
        def __init__(self, *args, settings=None, **kwargs):
            super().__init__(*args, **kwargs)
            self.settings = settings
    

    从你的例子看来 QObject.__init__ 没有任何必需的参数,但你仍然应该编写混入以在可选参数或在其他地方重用的情况下发挥良好的作用。然后 MyObject 实现看起来像:

    class MyObject(SettingsObject, QObject):
    
        def __init__(self, param):
            super().__init__(settings=Settings(file))
            self.param = param
    

    注意:

    • mix-in classes在subclassing时最先出现,所以MRO是MyObject -> SettingsObject -> QObject;和
    • 您调用 super().__init__ 一次,而不是单独调用每个超级class 实现。

作为替代方案,您是否考虑过组合?也许 MyObject 应该 采用 设置:

 class MyObject(QObject):

     def __init__(self, param, settings=None):
         super().__init__()
         self.param = param
         self.settings = settings

obj = MyObject('something', Settings(__file__))

现在您调用 self.settings.method(...) 而不是 self.method(...),但您没有多重继承引入的复杂性。