谈谈Python中的super

https://www.bilibili.com/video/BV1FL4y1E7xK 参考了码农高天的视频

先看简单示例

  • 代码0:基类的

     class A:
         def __init__(self,name):
             print('A init...')
             self.name = name
         def say(self):
             print(f'{self.name} saying')
         @classmethod
         def drink(cls):
             print('drinking...')
     
    
  • 代码1:子类(第一个版本)

     class B1(A):  #没有对super调用
         def __init__(self, name):
             print('B1 init...')
     
     b = B1('wuxianfeng')  #B1 init...
     b.drink()  #drinking...
     b.say()    #报错了,没属性name  #AttributeError: 'B1' object has no attribute 'name'
    
    • 你会输出B init…,但没有输出A init…,所以你这个继承丢失了父类A的初始化。
  • 对于pycharm这样的IDE,会提示你如下图:缺少对超类__init__的调用,你点击蓝色的添加超类调用,就会生成代码

    img

  • 代码2:添加了超类的调用

     class B2(A):  #有对super调用
         def __init__(self, name):
             super().__init__(name) #点击蓝色的添加超类调用产生的代码
             print('B2 init...')
     b = B2('wuxianfeng')  #A init...  # B2 init...
     b.drink()  #drinking...
     b.say()    #wuxianfeng saying
    
    • 你可以想见super()的意思就是B2的父类A,所以我们可以换个写法
  • 代码3:换一个写法的调用

     class B3(A):  #有对super调用
         def __init__(self, name):
             A.__init__(self,name)  #注意A没有(),init的调用self不能少,name也是要的(跟A的初始化函数有关)
             print('B3 init...')
     
     b = B3('wuxianfeng')
     b.drink()
     b.say()
    
  • 代码4:再换一个写法

     class B4(A):  #有对super调用
         def __init__(self, name):
             super(B4,self).__init__(name)   #注意super(当前类名,self).__init__(父类初始化需要的参数)
             print('B4 init...')
     b = B4('wuxianfeng')
     b.drink()
     b.say()
    

super的DOC

  • 所以super不是方法不是函数也不是关键字啥的,是个类!

  • 在IDE中点击super能得到如下提示,摘录部分,也就是super.doc

     class super(object):
         """
         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)
         Typical use to call a cooperative superclass method:
         class C(B):
             def meth(self, arg):
                 super().meth(arg)
         This works for class methods too:
         class C(B):
             @classmethod
             def cmeth(cls, arg):
                 super().cmeth(arg)
    
  • 四种用法

    • super(),等价于super(class, )
    • super(type),unbound super object
    • super(type,obj),bound super object,requires isinstance(obj, type)
    • super(type1,type2),bound super object,requires issubclass(type2, type)
  • 其实我们简单来说,你用的无非是1和3

  • 稍微进一步你可能要去理解bound和unbound

看另外一个示例

  • 示例代码

     from objprint import op   #打印对象信息
     
     
     class Person:
         def __init__(self, name):
             self.name = name
     
     
     class Male(Person):
         def __init__(self, name):
             #super().__init__(name)
             super(Male, self).__init__(name)   #这两句是一样的
             self.sex = 'male'
     
     
     wuhanyu = Male('wuhanyu')
     op(wuhanyu)
    
     <Male 0x22f114758e0
       .name = 'wuhanyu',
       .sex = 'male'
     >
    
  • super中有2个参数,第一个是type(也就是一个class),第二个是type或者ojbect,第二个参数决定了这个函数?bind到哪个object或者class上。同时第二个参数决定了使用哪个mro。第一个参数也决定了在mro这个链上从哪个class开始找。

  • 片段

     super().__init__(name)
     等价于
     super(Male, self).__init__(name) 
         Male是个class
         self是个object
    
  • super(Male,self)做的事情是,

    • 首先,从self这个object中拿到mro.如下,Male->Person->object;

    • 然后,找到Male在MRO中所处的位置(当前情况下就是第一个),然后找Male后面的(也就是Person,object)开始找,第一个找到的是Person,然后看Person中是否存在__init__这个函数,如果有(确实有),就把__init__这个函数bind到self上,

       import inspect
       print(inspect.getmro(Male)) 
       ===>(<class '__main__.Male'>, <class '__main__.Person'>, <class 'object'>)
      
    • 所以综上,又有一个等价关系

       super().__init__(name)
       等价于
       super(Male, self).__init__(name) 
       等价于
       Person.__init__(self,name)
      
  • 那么为何不用Person.的方式,用super比较好呢?super是动态的,Person就写死了,如果你哪天改变了继承关系,那Person.就不合适了。

    • 改变基类的名字
    • 改变继承的方式
    • super是动态的

关于super是动态的示例

  • 修改上面的代码

     from objprint import op
     
     class Animal:
         def __init__(self,age):
             self.age = age
     class Person(Animal):
         def __init__(self, name,age):
             super(Person, self).__init__(age)
             self.name = name
     
     
     class Male(Person):
         def __init__(self, name,age):
             # super().__init__(name,age)
             super(Male, self).__init__(name,age)
             self.sex = 'male'
     
     
     wuhanyu = Male('wuhanyu',18)
     op(wuhanyu)
    
    • 如果把15行改为如下

       super(Person, self).__init__(name,age)
      
    • 事实是报错了,因为根据前面说的MRO搜索,super(Person, self),从Person往后找它的MRO链,第一个是Animal,初始化的时候只需age参数,你现在传递了2个参数

    • 修改下

       class Male(Person):
           def __init__(self,age):   #此处改了
               #super().__init__(name,age)
               super(Person, self).__init__(age)  #此处改了
               self.sex = 'male'
       
       
       wuhanyu = Male(18)   #此处改了
       op(wuhanyu)
      
       <Male 0x238fd4e5d90
         .age = 18,
         .sex = 'male'
       >
      
      • 你会发现跳过了Person,没有name了,只有age和sex了!

super可以在class之外使用

  • 片段

     wuhanyu = Male(18)
     super(Male,wuhanyu).__init__('wuxianfeng',19)
     op(wuhanyu)
    
     <Male 0x1cc841870a0
       .age = 19,         #一开始是18,后来被覆盖了
       .name = 'wuxianfeng',
       .sex = 'male'
     >
    
  • 这句话的含义其实是,从m这个对象的mro上寻找Male后面的__init__函数,就对应了Person的,

特例

  • 代码

     class A:
         def say(self):
             print('A')
     
     class B(A):
         def say(self):
             super().say()
             #super(B,self).say()
     
     class C(A):
         def say(self):
             print('C')
     class M(B,C):
         def say(self):
             #super().say()
             B.say(self)
     m = M()
     m.say()
    
  • 上面的打印结果是什么?

  • 是C

  • B和C好像没有任何关系

  • 但是m这个实例对象,是M object,的父类是B和C,其MRO是B->C->A

  • 而B中的第七行,跟第八行是一样的,第八行中的self是m,

  • m找到B之后下面找的是C,C的say是输出c

  • 这么理解不知道是否能明白。

  • 所以很多人会认为super是调用父类的同名函数,这是不够准确的。

总结:

  • super的用法,有四种,13最常用
    • super()
    • super(type)
    • super(type,obj)
    • super(type1,type2)
  • super在多重继承(C->B->A)的时候可以选择性跳过某个父节点
  • super在多重继承(D->C|B->A)的时候要注意其MRO
  • 显式调用super,传入参数,搜索MRO
  • super中有2个参数,第一个是type(也就是一个class),第二个是type或者ojbect,第二个参数决定了这个函数?bind到哪个object或者class上。同时第二个参数决定了使用哪个mro。第一个参数也决定了在mro这个链上从哪个class开始找
  • super还可以在函数外调用
  • 难一些的是bound和unbound

附录1:super的源码

  • 在typeobject.c这个文件中

    PyTypeObject PySuper_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "super",                                    /* tp_name */
        sizeof(superobject),                        /* tp_basicsize */
        0,                                          /* tp_itemsize */
        /* methods */
        super_dealloc,                              /* tp_dealloc */
        0,                                          /* tp_vectorcall_offset */
        0,                                          /* tp_getattr */
        0,                                          /* tp_setattr */
        0,                                          /* tp_as_async */
        super_repr,                                 /* tp_repr */
        0,                                          /* tp_as_number */
        0,                                          /* tp_as_sequence */
        0,                                          /* tp_as_mapping */
        0,                                          /* tp_hash */
        0,                                          /* tp_call */
        0,                                          /* tp_str */
        super_getattro,                             /* tp_getattro */
        0,                                          /* tp_setattro */
        0,                                          /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
            Py_TPFLAGS_BASETYPE,                    /* tp_flags */
        super_doc,                                  /* tp_doc */
        super_traverse,                             /* tp_traverse */
        0,                                          /* tp_clear */
        0,                                          /* tp_richcompare */
        0,                                          /* tp_weaklistoffset */
        0,                                          /* tp_iter */
        0,                                          /* tp_iternext */
        0,                                          /* tp_methods */
        super_members,                              /* tp_members */
        0,                                          /* tp_getset */
        0,                                          /* tp_base */
        0,                                          /* tp_dict */
        super_descr_get,                            /* tp_descr_get */
        0,                                          /* tp_descr_set */
        0,                                          /* tp_dictoffset */
        super_init,                                 /* tp_init */
        PyType_GenericAlloc,                        /* tp_alloc */
        PyType_GenericNew,                          /* tp_new */
        PyObject_GC_Del,                            /* tp_free */
        .tp_vectorcall = (vectorcallfunc)super_vectorcall,
    };
    

附录2:super在pythondoc上的解释

英文版 super([type[, object-or-type]])

Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.

The object-or-type determines the method resolution order to be searched. The search starts from the class right after the type.

For example, if __mro__ of object-or-type is D -> B -> C -> A -> object and the value of type is B, then super() searches C -> A -> object.

The __mro__ attribute of the object-or-type lists the method resolution search order used by both getattr() and super(). The attribute is dynamic and can change whenever the inheritance hierarchy is updated.

If the second argument is omitted, the super object returned is unbound. If the second argument is an object, isinstance(obj, type) must be true. If the second argument is a type, issubclass(type2, type) must be true (this is useful for classmethods).

There are two typical use cases for super. In a class hierarchy with single inheritance, super can be used to refer to parent classes without naming them explicitly, thus making the code more maintainable. This use closely parallels the use of super in other programming languages.

The second use case is to support cooperative multiple inheritance in a dynamic execution environment. This use case is unique to Python and is not found in statically compiled languages or languages that only support single inheritance. This makes it possible to implement “diamond diagrams” where multiple base classes implement the same method. Good design dictates that such implementations have the same calling signature in every case (because the order of calls is determined at runtime, because that order adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime).

For both use cases, a typical superclass call looks like this:

class C(B):
    def method(self, arg):
        super().method(arg)    # This does the same thing as:
                               # super(C, self).method(arg)

In addition to method lookups, super() also works for attribute lookups. One possible use case for this is calling descriptors in a parent or sibling class.

Note that super() is implemented as part of the binding process for explicit dotted attribute lookups such as super().__getitem__(name). It does so by implementing its own __getattribute__() method for searching classes in a predictable order that supports cooperative multiple inheritance. Accordingly, super() is undefined for implicit lookups using statements or operators such as super()[name].

Also note that, aside from the zero argument form, super() is not limited to use inside methods. The two argument form specifies the arguments exactly and makes the appropriate references. The zero argument form only works inside a class definition, as the compiler fills in the necessary details to correctly retrieve the class being defined, as well as accessing the current instance for ordinary methods.

For practical suggestions on how to design cooperative classes using super(), see guide to using super().

中文版 super([type[, object-or-type]])

返回一个代理对象,它会将方法调用委托给 type 的父类或兄弟类。 这对于访问已在类中被重载的继承方法很有用。

object-or-type 确定用于搜索的 method resolution order。 搜索会从 type 之后的类开始。

举例来说,如果 object-or-type 的 __mro__ 为 D -> B -> C -> A -> object 并且 type 的值为 B,则 super() 将会搜索 C -> A -> object。

object-or-type 的__mro__ 属性列出了 getattr() 和 super() 所共同使用的方法解析搜索顺序。 该属性是动态的,可以在任何继承层级结构发生更新的时候被改变。

如果省略第二个参数,则返回的超类对象是未绑定的。 如果第二个参数为一个对象,则 isinstance(obj, type) 必须为真值。 如果第二个参数为一个类型,则 issubclass(type2, type) 必须为真值(这适用于类方法)。

super 有两个典型用例。 在具有单继承的类层级结构中,super 可用来引用父类而不必显式地指定它们的名称,从而令代码更易维护。 这种用法与其他编程语言中 super 的用法非常相似。

第二个用例是在动态执行环境中支持协作多重继承。 此用例为 Python 所独有而不存在于静态编码语言或仅支持单继承的语言当中。 这使用实现“菱形图”成为可能,即有多个基类实现相同的方法。 好的设计强制要求这样的方法在每个情况下都具有相同的调用签名(因为调用顺序是在运行时确定的,也因为这个顺序要适应类层级结构的更改,还因为这个顺序可能包括在运行时之前未知的兄弟类)。

对于以上两个用例,典型的超类调用看起来是这样的:

class C(B):
    def method(self, arg):
        super().method(arg)    # This does the same thing as:
                               # super(C, self).method(arg)

除了方法查找之外,super() 也可用于属性查找。 一个可能的应用场合是在上级或同级类中调用 描述器。

请注意 super() 是作为显式加点属性查找的绑定过程的一部分来实现的,例如 super().__getitem__(name)。 它做到这一点是通过实现自己的 __getattribute__() 方法,这样就能以可预测的顺序搜索类,并且支持协作多重继承。 对应地,super() 在像 super()[name] 这样使用语句或操作符进行隐式查找时则未被定义。

还要注意的是,除了零个参数的形式以外,super() 并不限于在方法内部使用。 两个参数的形式明确指定参数并进行相应的引用。 零个参数的形式仅适用于类定义内部,因为编译器需要填入必要的细节以正确地检索到被定义的类,还需要让普通方法访问当前实例。

对于有关如何使用 super() 来如何设计协作类的实用建议,请参阅 使用 super() 的指南。

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuxianfeng023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值