【问题标题】:Inheriting from classes with and without **kwargs从有和没有 **kwargs 的类继承
【发布时间】:2018-07-04 01:26:33
【问题描述】:

我正在用 Python 构建一个绘图类,并希望执行以下操作。我想要一个使用 PyQt5 的图形窗口,它也继承自我制作的一些自定义类(例如曲线拟合类)。为了使曲线拟合类能够处理绘图类中保留的数据,它必须具有对绘图类中包含的数据的引用。因此,我选择了从 CurveFitting 类继承的绘图类。

问题似乎出现在从 PyQt5 的 GraphicsWindow 类和我的自定义类中继承,它们接受不同数量的参数。我已经读到 Python 不能很好地处理使用“超级”功能继承不同数量参数的类,所以我决定让我的自定义 CurveFitting 类接受 **kwargs,然后给它一个对父级的引用。但是,然后我遇到了一个我不明白的不同错误。下面是我正在尝试做的整理示例

import numpy as np
from pyqtgraph import GraphicsWindow

class ClassA():
    def __init__(self, **kwargs):
        super().__init__()
        self.kwargs = kwargs
        self.parent = self.kwargs['parent']
        self.xdata = self.parent.xdata

    def print_data(self):
        print(self.parent.xdata)
        print(self.parent.ydata)


class classC(GraphicsWindow, ClassA):
    def __init__(self):
        kwargs = {}
        kwargs['parent'] = self
        kargs = kwargs
        self.xdata = np.linspace(0, 100, 101)
        self.ydata = np.linspace(0, 200, 101)

        super().__init__(**kwargs)
        # ClassA.__init__(self, **kwargs)
        # GraphicsWindow.__init__(self)

instC = classC()
instC.print_data()

当我运行上面的代码时,我在“super().__init(**kwargs)”行中得到“RuntimeError: super-class init() of type C is never called”老实说,我根本不明白,并尝试谷歌搜索了一段时间,但无济于事。

此外,我尝试注释掉该行,并取消注释接下来的两行以手动从每个类继承,但这也不起作用。我觉得很奇怪的是,如果我将这两条线中的一条注释掉,它们都可以单独工作,但它们不能一起工作。例如,如果我用两行都运行它,它会给我一个错误,即 kwargs 没有关键字 'parent',就好像它甚至没有通过 **kwargs。

有没有办法从两个采用不同数量的初始化参数的类继承?有没有完全不同的方法可以解决这个问题?谢谢。

【问题讨论】:

  • 关于代码的一些注释(因为它已经被归结,我很感激):ClassA 中的super().__init__() 的目的是什么? classC 中的kargs = kwargs 的用途是什么?您是否尝试过在classC 中交换基类的顺序(以防GraphicsWindow 可以很好地处理可变参数)?
  • 另外,thisthis 可能值得审查
  • (我误解了您的代码,现在看到您几乎将ClassA a 用作混合,所以应该回答第一个问题)。

标签: python class inheritance keyword-argument


【解决方案1】:

您的代码的直接问题是ClassC 继承自GraphicsWindow 作为它的第一个基类,ClassA 是第二个基类。当您调用 super 时,只有一个被调用 (GraphicsWindow),如果它不是为使用多重继承而设计的(似乎是这种情况),它可能不会调用 super 本身或可能不会传递ClassA 期望的参数。

只需切换基类的顺序就足以使其工作。 Python 保证基类将按照它们出现在class 语句中的相同相对顺序被调用(尽管如果稍后发生更多继承,则可以在 MRO 中的它们之间插入其他类)。由于ClassA.__init__ 确实调用了super,它应该会更好!

__init__ 方法与多重继承一起工作可能很棘手,即使涉及的所有类都设计为使用它。这就是为什么经常避免位置参数的原因,因为它们的顺序会变得非常混乱(因为子类只能在其父位置参数之前添加位置参数,除非他们想要重复所有名称)。使用关键字参数绝对是更好的方法。

但是您拥有的代码使处理关键字参数变得比应有的复杂一些。您不需要显式创建字典以使用**kwargs 语法传递,也不需要从您接受**kwargs 参数的字典中提取关键字值。通常每个函数都应该命名它所接受的参数,并且只对未知参数使用**kwargs(MRO 中的其他类可能需要)。看起来是这样的:

class Base1:
    def __init__(self, *, arg1, arg2, arg3, **kwargs):  # the * means the other args are kw-only
        super().__init__(**kwargs)                      # always pass on all unknown arguments
        ...                                             # use the named args here (not kwargs)

class Base2:
    def __init__(self, *, arg4, arg5, arg6, **kwargs):
        super().__init__(**kwargs)
        ...

class Derived(Base1, Base2):
    def __init__(self, *, arg2, arg7, **kwargs):         # child can accept args used by parents
        super().__init__(arg2=arg2+1, arg6=3, **kwargs)  # it can then modify or create from
        ...                                              # scratch args to pass to its parents

obj = Derived(arg1=1, arg2=2, arg3=3, arg4=4, arg5=5, arg7=7) # note, we've skipped arg6
                                                              # and Base1 will get 3 for arg2

但我也会认真考虑继承在您的情况下是否有意义。将您的两个基类之一封装在您的子类中而不是继承自其中可能更有意义。也就是说,您将只继承ClassAGraphicsWindow 中的一个,并将另一个实例存储在ClassC 的每个实例中。 (您甚至可以从两个基类中都继承,并将它们都封装起来。)封装通常比继承更容易推理和正确。

【讨论】:

  • 谢谢,我认为我在这里学到的最好的东西是在你不需要的时候不一定要依赖继承。我能够通过在我的绘图类中创建一个 GraphicsWindow 的实例来实现相同的功能,然后这绕过了所有其他问题。我能够通过切换 GraphicsWindow 和我的自定义类的顺序来运行该类,但是我不理解的进一步复杂性开始发生。
猜你喜欢
  • 2016-02-09
  • 2022-09-22
  • 1970-01-01
  • 2011-01-14
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 2013-01-27
  • 1970-01-01
相关资源
最近更新 更多