super()和更改合作方法的签名 [英] super() and changing the signature of cooperative methods

查看:112
本文介绍了super()和更改合作方法的签名的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

示例摘自文章 super()被视为有害文章

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)

解决方案

詹姆斯·奈特(James Knight)的文章 super()被认为是有害的通过始终在所有协作功能中接受*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(),否则我们可能在层次结构中调用某些函数零次,或多次调用

    /li>
  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.

这是基于此更严格规则的解决方案.首先从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

这是正确的

in a multiple inheritance setting such as laid out in, how can I use super() and also handle the case when the signature of the function changes between classes in the hierarchy?

i.e. can I rewrite this example (in python3) to work with super()?

example was taken from the article 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)

解决方案

James Knight's article super() considered harmful suggests a solution by always accepting *args and **kwargs in all cooperating functions. however this solution does not work for two reasons:

  1. object.__init__ does not accept arguments this is a breaking change introduced python 2.6 / 3.x TypeError: object.__init__() takes no parameters

  2. using *args is actually counter productive

Solution TL;DR

  1. super() usage has to be consistent: In a class hierarchy, super should be used everywhere or nowhere. is part of the contract of the class. if one classes uses super() all the classes MUST also use super() in the same way, or otherwise we might call certain functions in the hierarchy zero times, or more than once

  2. to correctly support __init__ functions with any parameters, the top-level classes in your hierarchy must inherit from a custom class like 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. if overridden functions in the class hierarchy can take differing arguments, always pass all arguments you received on to the super function as keyword arguments, and, always accept **kwargs.

Here's a rewritten example

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)

output:

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

Discussion

lets look at both problems in more detail

object.__init__ does not accept arguments

consider the original solution given by James Knight:

the general rule is: always pass all arguments you received on to the super function, and, if classes can take differing arguments, always accept *args and **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)

a breaking change in python 2.6 and 3.x has changed object.__init__ signature so that it no longer accepts arbitrary arguments

---------------------------------------------------------------------------
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)

The correct way to handle this conundrum is for the top level classes in a hierarchy to inherit from a custom class like 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()

and thus rewriting the example as follows should work

    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)

output:

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

using *args is counter productive

lets make the example a bit more complicated, with two different parameters: name and age

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)

output:

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

as you can see from the line C age=python the positional arguments got confused and we're passing the wrong thing along.

my suggested solution is to be more strict and avoid an *args argument altogether. instead:

if classes can take differing arguments, always pass all arguments you received on to the super function as keyword arguments, and, always accept **kwargs.

here's a solution based on this stricter rule. first remove *args from 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()

and now remove *args from the rest of the classes, and pass arguments by name only

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)

output:

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

which is correct

这篇关于super()和更改合作方法的签名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆