【问题标题】:super() and changing the signature of cooperative methodssuper() 和更改合作方法的签名
【发布时间】:2019-11-04 22:52:25
【问题描述】:

在layout等多重继承设置中,如何使用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)

【问题讨论】:

  • 请包含代码而不是添加链接,为什么您认为它有害?

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


【解决方案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(),否则我们可能会调用层次结构中的某些函数零次,或者不止一次

  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 会适得其反

让示例稍微复杂一点,使用两个不同的参数: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

这里有一个基于这个更严格规则的解决方案。首先从SuperObject中删除*args

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

这是正确的

【讨论】:

    【解决方案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)
    

    【讨论】:

    • 不幸的是,这段代码是错误的,因为在构造 e 时没有调用 B.__init__ 和 D.__init__。只需运行最后一行 e = E(1, 4, 5, 5, 5, 5, value=4) 并查看丢失的调用
    猜你喜欢
    • 2018-01-13
    • 1970-01-01
    • 1970-01-01
    • 2016-09-06
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-06
    相关资源
    最近更新 更多