正确的使用超级方法(参数传递)


99

所以我一直在关注Python的超级有害,并去测试他的例子。

但是,示例1-3假定super在处理__init__需要不同参数的方法时显示正确的调用方式,但完全无效。

这是我得到的:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

看来它object本身违反了文档中提到的最佳实践之一,即使用的方法super必须接受*args**kwargs

现在,很明显,奈特先生希望他的例子行得通,这是否在最新版本的Python中有所改变?我检查了2.6和2.7,但两者均失败。

那么解决这个问题的正确方法是什么?


2
我的首选方式是:平坦而简单的继承层次结构。
millimoose 2012年

19
你也应该阅读“Python的超()超认为是”获得平衡的观点:)
比约恩博动

@BjörnPollex:谢谢!您的链接提供了一个问题的答案:你可以写一个“根类”,从继承object,它会确保呼叫object__init__正确。
cha0site 2012年

4
请注意,__init__on会object静默忽略Python 2.5上的任何参数。这在Python 2.6中已更改。
Wilfred Hughes 2014年

1
@Wilfred:嘿,谢谢您回答实际的问题!现在我知道为什么这篇文章过时了!
cha0site 2014年

Answers:


103

有时两个类可能有一些相同的参数名称。在这种情况下,您无法将键值对从中弹出**kwargs或从中删除*args。相反,您可以定义一个Base不同于object,吸收/忽略参数的类:

class Base(object):
    def __init__(self, *args, **kwargs): pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

产量

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

请注意,要使其正常工作,Base必须是MRO中的倒数第二个类。


4
不应该Base打电话super(Base, self).__init__()吗?
cha0site 2012年

4
为此,Base必须在MRO的末尾(除外object)。调用object.__init__没有任何作用,因此可以不调用super(Base, self).__init__()。实际上,我认为不包括super(Base, self).__init__()将终点Base作为终点可能更清楚。
unutbu 2012年

25

如果您要继承很多东西(在这里就是这种情况),建议您使用传递所有参数**kwargs,然后pop在使用它们后立即传递它们(除非在上层类中需要它们)。

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

这是解决这类问题的最简单方法。

third = Third(first_arg=1, second_arg=2, third_arg=3)

6

正如Python在super()中所认为的super所解释的那样,一种方法是让类吃掉它所需的参数,然后传递其余的参数。因此,当调用链到达时object,所有参数都被吃掉了,并且object.__init__将在不带参数的情况下调用它(如预期的那样)。因此,您的代码应如下所示:

class A(object):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(object):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(*args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(*args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(*args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)

那么,那又如何E(arg = 10)呢?我不喜欢这种方法,我需要事先了解MRO才能使用它。我编写从中继承的“根类”的另一种方法object看起来更干净。
cha0site 2012年

如果被调用的函数不共享参数名,则此方法很有用。我承认我没有太多的实践经验super,因此这种方法的确可能是理论上的。
比约恩博动
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.