• 你会使用super()吗?你确定你了解它吗?


    我们经常在类的继承当中使用super(), 来调用父类中的方法。例如下面:

    class A:
        def func(self):
            print('OldBoy')
    
    
    class B(A):
        def func(self):
            super().func()
            print('LuffyCity')
    
    
    A().func()
    B().func()

    输出的结果为:

    OldBoy
    OldBoy
    LuffyCity

    A实例化的对象调用了func方法,打印输出了 Oldboy

    B实例化的对象调用了自己的func方法,先调用了父类的方法打印输出了 OldBoy ,再打印输出 LuffyCity 。

    这样是Python3的写法,今天咱们也只讨论Python3中的super。

    如果不使用super的话,想得到相同的输出截个,还可以这样写B的类:

    class B(A):
        def func(self):
            A.func(self)
            print('LuffyCity')

    这样能实现相同的效果,只不过传了一个self参数。那为什么还要使用super()呢?

    那我看看有这样的一个继承关系的类(钻石继承):

          Base
          /  
         /    
        A      B
             /
            /
           C

    代码是这样的:

    class Base:
        def __init__(self):
            print('Base.__init__')
    
    
    class A(Base):
        def __init__(self):
            Base.__init__(self)
            print('A.__init__')
    
    
    class B(Base):
        def __init__(self):
            Base.__init__(self)
            print('B.__init__')
    
    
    class C(A, B):
        def __init__(self):
            A.__init__(self)
            B.__init__(self)
            print('C.__init__')
    
    
    C()

    输出的结果是:

    Base.__init__
    A.__init__
    Base.__init__
    B.__init__
    C.__init__

    每个子类都调用父类的__init__方法,想把所有的初始化操作都做一遍,但是出现了一个问题,Base类的__init__方法被调用了两次,这是多余的操作,也是不合理的。

    那我们改写成使用super()的写法:

    class Base:
        def __init__(self):
            print('Base.__init__')
    
    
    class A(Base):
        def __init__(self):
            super().__init__()
            print('A.__init__')
    
    
    class B(Base):
        def __init__(self):
            super().__init__()
            print('B.__init__')
    
    
    class C(A, B):
        def __init__(self):
            super().__init__()
            print('C.__init__')
    
    
    C()

    输出的结果是:

    Base.__init__
    B.__init__
    A.__init__
    C.__init__ 

    这样执行的结果就比较满意,是大多数人想要的结果。那为什么会是这样的结果呢?

    那是因为我们每定义一个类的时候,Python都会创建一个MRO列表,用来管理类的继承顺序。

    print(C.mro())
    # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>]

    Python通过这个列表从左到右,查找继承的信息。Python3中的类都是新式类,都有这个mro属性,能看出来是广度优先的查找原则。经典类就没有mro属性,但它的查找原则是深度优先。

    那我回到super的问题上来,让我们先看看super的官方定义

     super([type[, object-or-type]]) 

    返回一个代理对象,该对象将方法调用委托给类的父类或兄弟类。这对于访问类中已重写的继承方法非常有用。搜索顺序与getattr()使用的搜索顺序相同,只是类型本身被跳过。

    类的__mro__属性列出了getattr()和super()使用的方法解析搜索顺序。属性是动态的,可以在继承层次结构更新时进行更改。

    看到官方的解释就可以很清楚的明白,super是一个类,实例化之后得到的是一个代理的对象,而不是得到了父类,并且我们使用这个代理对象来调用父类或者兄弟类的方法。

    那我们再看看super的使用方法:

    super() -> same as super(__class__, <first argument>)
    super(type) -> unbound super object
    super(type, obj) -> bound super object; requires isinstance(obj, type)
    super(type, type2) -> bound super object; requires issubclass(type2, type)

    super至少需要一个参数,并且类型需要是类。

    不传参数的会报错。只传一个参数的话是一个不绑定的对象,不绑定的话也就没什么用了。

    print(super(C))
    print(super())

    输出结果:

    RuntimeError: super(): no arguments
    <super: <class 'C'>, NULL>

    在定义类当中可以不写参数,Python会自动根据情况将两个参数传递给super。

    class C(A, B):
        def __init__(self):
            print(super())
            super().__init__()
            print('C.__init__')
    
    
    C()

    输出结果:

    <super: <class 'C'>, <C object>>
    Base.__init__
    B.__init__
    A.__init__
    C.__init__

    所以我们在类中使用super的时候参数是可以省略的。

    第二种用法, super(type, obj) 传递一个类和对象,得到的是一个绑定的super对象。这还需要obj是type的实例,可以不是直接的实例,是子类的实例也行。

    a = A()
    print(super(A, a))
    print(super(Base, a))

    输出结果:

    Base.__init__
    A.__init__
    <super: <class 'A'>, <A object>>
    <super: <class 'Base'>, <A object>>

    第三种用法, super(type, type2)传递两个类,得到的也是一个绑定的super对象。这需要type2是type的子类。

    print(super(Base, A))
    print(super(Base, B))
    print(super(Base, C))

     输出结果:

    <super: <class 'Base'>, <A object>>
    <super: <class 'Base'>, <B object>>
    <super: <class 'Base'>, <C object>>

    接下来我们就该说说查找顺序了,两个参数,是按照那个参数去计算MRO呢?

    我们将C类中的super的参数填写上,并且实例化,看看输出的结果。

    class C(A, B):
        def __init__(self):
            super(C, self).__init__()
            print('C.__init__')

     输出结果:

    Base.__init__
    B.__init__
    A.__init__
    C.__init__

    看结果和之前super没填参数的结果是一样的。

    那我们将super的第一个参数改为A:

    class C(A, B):
        def __init__(self):
            super(A, self).__init__()
            print('C.__init__')
    

    输出结果:

    Base.__init__
    B.__init__
    C.__init__

    咦!?那A.__init__怎么跑丢了呢?多出来了B.__init__呢?

    这是应为Python是按照第二个参数来计算MRO,这次的参数是self,也就是C的MRO。在这个顺序中跳过一个参数(A)找后面一个类(B),执行他的方法。

    知道这个后,输出的结果就可以理解了。 super(A, self).__init__() 没有执行Base的方法,而是执行了B的方法。


    那我们接下来说说 super(type, obj)  和 super(type, type2)的区别。

    代码如下:

    class Base:
        def func(self):
            return 'from Base'
    
    
    class A(Base):
        def func(self):
            return 'from A'
    
    
    class B(Base):
        def func(self):
            return 'from B'
    
    
    class C(A, B):
        def func(self):
            return 'from C'
    
    
    c_obj = C()
    
    print(super(C, C))
    print(super(C, c_obj))

    输出结果:

    <super: <class 'C'>, <C object>>
    <super: <class 'C'>, <C object>>

    两次的打印结果一模一样,verygood。那他们的方法是否是一样的呢?测试一下。

    print(super(C, C).func is super(C, c_obj).func)
    print(super(C, C).func == super(C, c_obj).func)

    输出结果:

    False
    False

    他俩的方法既不是指向同一个,值还不相等。是不是搞错了呢?再试试下面的看看。

    c1 = super(C, C)
    c2 = super(C, C)
    print(c1 is c2)
    print(c1 == c2)
    print(c1.func is c2.func)
    print(c1.func == c2.func)

    输出结果:

    False
    False
    True
    True

     c1和c2不是一个对象,但是他们的方法却是相同的。

    那 super(C, C).func 和 super(C, c_obj).func 的确是不同的。那打印出来看看有什么区别:

    print(super(C, C).func)
    print(super(C, c_obj).func)

     输出结果:

    <function A.func at 0x0000000009F4D6A8>
    <bound method A.func of <__main__.C object at 0x00000000022A94E0>>

     super的第二个参数传递的是类,得到的是函数。

     super的第二个参数传递的是对象,得到的是绑定方法。

     函数和绑定方法的区别就不再赘述了,在这里想得到一样的结果,只需要给函数传递一个参数,而绑定方法则不需要传递额外的参数了。

    print(super(C, C).func(c_obj))
    print(super(C, c_obj).func())

     输出结果:

    from A
    from A

    那我现在总结一下:

    1. super()使用的时候需要传递两个参数,在类中可以省略不写,我们使用super()来找父类或者兄弟类的方法;
    2. super()是根据第二个参数来计算MRO,根据顺序查找第一个参数类后的方法。
    3. super()第二个参数是类,得到的方法是函数,使用时要传self参数。第二个参数是对象,得到的是绑定方法,不需要再传self参数。

    给使用super()的一些建议:

    1. super()调用的方法要存在;
    2. 传递参数的时候,尽量使用*args 与**kwargs;
    3. 父类中的一些特性,比如【】、重写了__getattr__,super对象是不能使用的。
    4. super()第二个参数传的是类的时候,建议调用父类的类方法和静态方法。
  • 相关阅读:
    Akka源码分析-Extension
    Akka源码分析-Remote-Creating Actors Remotely
    24-2 show构造方法
    day24-1构造方法
    day23-4 最小值-到最大值排序
    day23-4 最小值-到最大值冒泡排序
    day23-3 最大值-到最小值排序
    day23-2 倒叙
    day23-1 水仙花
    day22 随机输出ArrayList
  • 原文地址:https://www.cnblogs.com/maple-shaw/p/9288018.html
Copyright © 2020-2023  润新知