不同init参数的多重继承的真正解决方案

y-s*_*een 5 python initialization multiple-inheritance python-3.x

这似乎是一个如此简单的任务,但我现在在这上面花了太多时间,没有解决方案。这是设置:

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)
Run Code Online (Sandbox Code Playgroud)

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

TypeError: __init__() got an unexpected keyword argument 'y'
Run Code Online (Sandbox Code Playgroud)

其他所有尝试都失败了类似的方式,我可以找到没有用正确的解决方案在互联网上的资源。唯一的解决方案包括将所有 init 参数替换为*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")
Run Code Online (Sandbox Code Playgroud)

结果:

Entering Init
Super Init True
Super Init False
Leaving Init
Run Code Online (Sandbox Code Playgroud)

我希望这条线Super Init True消失。

jon*_*rpe 5

您似乎对一些事情感到困惑,因此:

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

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

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

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

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

  3. 你如何编写混合类?

    我认为这才是你真正应该问的问题。SettingsObject应该是一个混合类,因此被正确设计以与它混合到的层次结构中的其他类协作。在这种情况下:

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

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

    class MyObject(SettingsObject, QObject):
    
        def __init__(self, param):
            super().__init__(settings=Settings(file))
            self.param = param
    
    Run Code Online (Sandbox Code Playgroud)

    注意:

    • 混合类在子类化时首先出现,因此 MRO 是MyObject-> SettingsObject-> QObject;和
    • 您调用super().__init__一次,而不是分别调用每个超类实现。

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

 class MyObject(QObject):

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

obj = MyObject('something', Settings(__file__))
Run Code Online (Sandbox Code Playgroud)

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