super()和更改合作方法的签名

时间:2019-06-22 09:51:04

标签: python python-3.x multiple-inheritance super

在诸如布局的多重继承设置中,如何使用super()并处理函数的签名在层次结构中的类之间更改的情况?

即我可以重写此示例(在python3中)以与super()一起使用吗?

  

示例摘自文章super() considered harmful article

class A():
    def __init__(self):
        print("A")

class B(object):
    def __init__(self):
        print("B")

class C(A):
    def __init__(self, arg):
        print("C","arg=",arg)
        A.__init__(self)

class D(B):
    def __init__(self, arg):
        print("D", "arg=",arg)
        B.__init__(self)

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

E(10)

2 个答案:

答案 0 :(得分:-1)

James Knight的文章super() considered harmful提出了一种解决方案,即在所有协作功能中始终接受*args**kwargs。 但是,此解决方案无法工作的原因有两个:

  1. object.__init__不接受参数 这是python 2.6 / 3.x引入的重大变化 TypeError: object.__init__() takes no parameters

  2. 使用*args实际上会适得其反

解决方案TL; DR

  1. super()的用法必须保持一致:在类层次结构中,super应该在任何地方或任何地方使用。是班级合同的一部分。如果一个类使用super(),所有类必须也都以相同的方式使用super(),否则我们可能将层次结构中的某些函数调用0次,或多次调用< / p>

  2. 要正确支持带有任何参数的__init__函数,层次结构中的顶级类必须继承自诸如SuperObject的自定义类:

    class SuperObject:        
        def __init__(self, **kwargs):
            mro = type(self).__mro__
            assert mro[-1] is object
            if mro[-2] is not SuperObject:
                raise TypeError(
                    'all top-level classes in this hierarchy must inherit from SuperObject',
                    'the last class in the MRO should be SuperObject',
                    f'mro={[cls.__name__ for cls in mro]}'
                )
    
            # super().__init__ is guaranteed to be object.__init__        
            init = super().__init__
            init()
    
  3. 如果类层次结构中的重写函数可以采用不同的参数,请始终将接收到的所有参数作为关键字参数传递给超函数,并始终接受**kwargs

这是一个重写的示例

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

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

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

e = E(name='python', age=28)

输出:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

讨论

让我们更详细地研究这两个问题

object.__init__不接受参数

考虑詹姆斯·奈特(James Knight)给出的原始解决方案:

  

一般规则是:始终将接收到的所有参数传递给超函数,并且,如果类可以采用不同的参数,则始终接受*args**kwargs

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

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

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

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

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

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

python 2.6和3.x中的重大更改更改了object.__init__签名,以使其不再接受任意参数

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-9001c741f80d> in <module>
     25 
     26 print( "MRO:", [x.__name__ for x in E.__mro__])
---> 27 E(10)

...

<ipython-input-2-9001c741f80d> in __init__(self, *args, **kwargs)
      7     def __init__(self, *args, **kwargs):
      8         print("B")
----> 9         super(B, self).__init__(*args, **kwargs)
     10 
     11 class C(A):

TypeError: object.__init__() takes exactly one argument (the instance to initialize)

处理此难题的正确方法是使层次结构中的顶级类从SuperObject之类的自定义类继承:

class SuperObject:        
    def __init__(self, *args, **kwargs):
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

并因此将以下示例重写为应该

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

    class B(SuperObject):
        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', 'SuperObject', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
SuperObject

使用*args会产生反作用

let使用两个不同的参数使示例更加复杂:nameage

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

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

class C(A):
    def __init__(self, age, *args, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age, *args, **kwargs)

class D(B):
    def __init__(self, name, *args, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name, *args, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name, age, *args, **kwargs)

E('python', 28)

输出:

E name=python age=28
C age=python
A
D name=python
B
SuperObject

C age=python行中可以看到,位置参数很混乱,我们正在传递错误的信息。

我建议的解决方案是更加严格,并且完全避免使用*args参数。代替:

  

如果类可以采用不同的参数,则始终将接收到的所有参数作为关键字参数传递给超函数 ,并始终接受**kwargs

这是基于此更严格规则的解决方案。首先从*args删除SuperObject

class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

,现在从其余的类中删除*args,并仅按名称传递参数

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

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

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

输出:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

这是正确的

答案 1 :(得分:-2)

请参见以下代码,这是否回答了您的问题?

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

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

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

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

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


# now you can call the classes with a variable amount of arguments
# which are also delegated to the parent classed through the super() calls
a = A(5, 5)
b = B(4, 4)
c = C(1, 2, 4, happy=True)
d = D(1, 3, 2)
e = E(1, 4, 5, 5, 5, 5, value=4)