【问题标题】:Python inheritance structure and argumentsPython继承结构和参数
【发布时间】:2020-05-16 16:46:13
【问题描述】:

我正在尝试设计一个类结构,允许用户定义自己的类,该类重载其他类中的预定义方法。在这种情况下,用户将创建 C 类以重载 D 中的“函数”方法。用户创建的 C 类与其他用户创建的类 A 和 B 具有共同逻辑,因此它们从 C 继承以重载“函数”,但也继承自D 使用 D 的其他方法。我遇到的问题是如何将“值”从 A 和 B 传递给 D,而忽略将其传递给 C。我目前写的内容会产生错误,因为 C 没有“值”作为参数。

我知道我可以将“值”(或 *args)添加到 C 的 init 方法和超级调用,但我不想知道其他类需要什么输入才能将新类添加到 A 和 B . 另外,如果我交换 C 和 DI 的顺序不会出错,但是我不使用 C 的重载“函数”。有没有明显的解决方法?

class D(SomethingElse):
    def __init__(self, value, **kwargs):
        super(D, self).__init__(**kwargs)

        self.value = value

    def function(self):
        return self.value

    def other_method(self):
        pass

class C(object):
    def __init__(self):
        super(C, self).__init__()

    def function(self):
        return self.value*2

class B(C, D):
    def __init__(self, value, **kwargs):
        super(B, self).__init__(value, **kwargs)

class A(C, D):
    def __init__(self, value, **kwargs):
        super(A, self).__init__(value, **kwargs)



a = A(3)
print(a.function())
>>> 6

【问题讨论】:

  • 输出应该是什么?
  • 我应该是 6 岁。我编辑了帖子以反映这一点。
  • 好像是python2?它得到了不同的错误。我在super(A, self).__init__(value, **kwargs)__init__() takes 1 positional argument but 2 were given 上的class A 出现错误,因为super(A, self) 试图引用class C,而它的__init__ 没有收到任何位置参数。我必须将*args, **kwargs 添加到mehtioned __init__ 并写super(C, self).__init__(*args, **kwargs) 并且一切正常。这是python3.6。
  • 我知道它不起作用。这就是我发布的内容。我也知道添加 *ags 可以解决此问题。我只是想知道是否可以添加这些。 C 是自包含的,只是重载方法,它不需要知道输入参数是什么。我想知道是否有一种方法可以在不需要 args 和 kwargs 的情况下对其进行编码。
  • 有方法解析顺序(MRO)。当函数丢失时,python 会尝试在其他地方搜索它(在基类中,它的基类等等)。当您写 super(YourClass, self) 时,您指的是 MRO 中的下一个类。你可以写print(A.__mro__)你会得到[A, C, D, SomethingElse, object]。因此,class C 应该知道class D 应该收到的参数。最方便的方法是使用*args, **kwargs。请阅读,这个realpython.com/lessons/multiple-inheritance-python,可能包含更多细节。

标签: python multiple-inheritance


【解决方案1】:

所以我试图理解 A 和 B 的意义。我猜你可能想混合超类行为,有时还有本地行为。所以假设 A 只是混合了一些行为,而 B 有一些局部行为和状态。

如果您不需要自己的状态,则可能不需要__init__。所以对于AC 只需省略__init__

class SomethingElse(object):
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

class D(SomethingElse):
    def __init__(self, value, *args,  **kwargs):
        super(D, self).__init__(*args, **kwargs)

        self.value = value

    def function(self):
        return self.value

    def other_method(self):
        return self.__dict__

class C(object):
    #def __init__(self):
    #    super(C, self).__init__()

    def function(self):
        return self.value*2

class B(C, D):
    def __init__(self, value, bstate, *args, **kwargs):
        super(B, self).__init__(value, *args, **kwargs)
        self.bstate = bstate

    def __repr__(self):
        return (self.__class__.__name__ + ' ' +
                self.bstate + ' ' + str(self.other_method()))

class A(C, D):
    pass


a = A(3)
b = B(21, 'extra')

a.function()
6

b.function()
42
repr(a)
'<xx.A object at 0x107cf5e10>'
repr(b)
"B extra {'args': (), 'bstate': 'extra', 'value': 21, 'kwargs': {}}"

假设您可能仍在使用 python2 语法,我一直保留它,但正如另一个答案指出的那样,python3 简化了 super() 语法,您现在真的应该使用 python3。

如果您交换 C 和 D,您将更改 python 方法解析顺序,这确实会更改对 A.function 的调用所解析的方法。

【讨论】:

    【解决方案2】:

    基本上,您需要做两件事才能使您的 __init__ 方法与 Python 中的多重继承相得益彰:

    1. 始终采用**kwargs 参数,并始终调用super().__init__(**kwargs)even if you think you are the base class。仅仅因为你的超类是object 并不意味着你是method resolution order 中的最后一个(在object 之前)。
    2. 不要显式传递父类的__init__ 参数; only pass them via **kwargs。您的父类在方法解析顺序中不一定在您之后,因此位置参数可能会传递给错误的其他 __init__ 方法。

    这称为“合作子类化”。让我们试试你的示例代码:

    class D:
        def __init__(self, value, **kwargs):
            self.value = value
            super().__init__(**kwargs)
    
        def function(self):
            return self.value
    
    class C:
        # add **kwargs parameter
        def __init__(self, **kwargs):
            # pass kwargs to super().__init__
            super().__init__(**kwargs)
    
        def function(self):
            return self.value * 2
    
    class B(C, D):
        # don't take parent class's value arg explicitly
        def __init__(self, **kwargs):
            # pass value arg via kwargs
            super().__init__(**kwargs)
    
    class A(C, D):
        # don't take parent class's value arg explicitly
        def __init__(self, **kwargs):
            # pass value arg via kwargs
            super().__init__(**kwargs)
    

    演示:

    >>> a = A(value=3)
    >>> a.value
    3
    >>> a.function()
    6
    

    请注意,value 必须作为关键字参数而不是位置参数传递给 A 构造函数。还建议在调用super().__init__之前设置self.value = value

    我还将class C(object): 简化为class C:,将super(C, self) 简化为super(),因为它们在Python 3 中是等效的。

    【讨论】:

    • 这没有回答问题,即如何完全避免在仍然使用super的同时更改C.__init__的签名。
    • @ekhumoro 我没有看到问题的任何部分表明或暗示C.__init__ 的签名无法更改。我不确定你是如何从这个问题中得到的;请解释一下。
    • 第 2 段的第 1 句(以及 cmets,*args, **kwargs 等已经讨论过)。
    • “我不想知道其他类需要什么输入才能将新类添加到 A 和 B” 这是您的意思吗?并不是说签名不能改变,只是C.__init__ 不应该与其他__init__ 方法的签名耦合;并且使用kwargs 意味着它不是。 C 不知道也不关心 kwargs 中的内容。
    • 如果您也阅读了 cmets,您会发现 OP 希望避免将 **kwargs 作为要求:“我想知道是否有一种方法可以在不需要的情况下对此进行编码args 和 kwargs”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-16
    • 2012-03-06
    • 2017-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多