关于python:使用super的正确方法(参数传递)

correct way to use super (argument passing)

所以我跟踪了python的super-considered有害代码,并测试了他的示例。

但是,示例1-3在处理需要不同参数的__init__方法时,它应该显示正确的调用super的方法,但这种简单的方法不起作用。

这就是我得到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
~ $ 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,但都失败了。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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)

产量

1
2
3
4
5
6
MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

请注意,要使其生效,Base必须是MRO中的倒数第二类。


如果你要有很多继承权(这里就是这样),我建议你使用**kwargs传递所有参数,然后在使用之后立即传递这些参数(除非你在上层需要它们)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

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

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


正如在python的super()中所解释的,一种方法是让类接受它需要的参数,并传递其余的参数。因此,当调用链到达object时,所有参数都已被吃掉,并且将无参数地调用object.__init__(如预期的那样)。所以您的代码应该如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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)