zoukankan      html  css  js  c++  java
  • [python] super() 用法

    问题的发现与提出

    在Python类的方法(method)中,要调用父类的某个方法,在Python 2.2以前,通常的写法如下:

    class A:
        def __init__(self):
            print "enter A"
            print "leave A"
    
    class B(A):
        def __init__(self):
            print "enter B"
            A.__init__(self)
            print "leave B"
    
     >>> b = B()
     enter B
     enter A
     leave A
     leave B
    

    即,使用非绑定的类方法(用类名来引用的方法),并在参数列表中,引入待绑定的对象(self),从而达到调用父类的目的。

    这样做的缺点是,当一个子类的父类发生变化时(如类B的父类由A变为C时),必须遍历整个类定义,把所有的通过非绑定的方法的类名全部替换过来,例如:

    class B(C):    # A --> C
        def __init__(self):
            print "enter B"
            C.__init__(self) # A --> C
            print "leave B"
    

    如果代码简单,这样的改动或许还可以接受。但如果代码量庞大,这样的修改可能是灾难性的。因此,自Python 2.2开始,Python添加了一个关键字super,来解决这个问题。下面是Python 2.3的官方文档说明:

    super(type[, object-or-type])
      Return the superclass of type. 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. super() only works for new-style classes.
      A typical use for calling a cooperative superclass method is:
    class C(B):
    	def meth(self, arg):
    	super(C, self).meth(arg)
    

    为什么要调用父类?

    在类继承时, 要是重定义某个方法, 这个方法就会覆盖掉父类的相应同名方法. 通过调用父类实例, 可以在子类中同时实现父类的功能.例如:

    Should be new-class based on object in python2.
    class A(object):
        def __init__(self):
        	print "enter A"
        	print "leave A"
    
    class B(A):
        def __init__(self):
        	print "enter B"
        	super(B, self).__init__()
        	print "leave B"
    
    >>> b = B()
    enter B
    enter A
    leave A
    leave B
    

    通过调用super获得父类实例从而可以实现该实例的初始化函数,这在实践中太常用了 (因为要继承父类的功能,又要有新的功能)。

    直接使用父类来调用的差异

    通过父类类名调用方法很常用, 比较直观. 但其效果和super还是有差异的. 例如:

    class A(object):
        def __init__(self):
            print "enter A"
            print "leave A"
    
    class B(A):
        def __init__(self):
            print "enter B"
        	A.__init__(self)
        	print "leave B"
    
    class C(A):
        def __init__(self):
            print "enter C"
        	A.__init__(self)
        	print "leave C"
    
    class D(B,C):
        def __init__(self):
            print "enter D"
        	B.__init__(self)
        	C.__init__(self)
        	print "leave D"
    >>> d=D()
    enter D
    enter B
    enter A
    leave A
    leave B
    enter C
    enter A
    leave A
    leave C
    leave D
    

    可以发现,这里面A的初始化函数被执行了两次。因为我们同时要实现B和C的初始化函数,所以分开调用两次,这是必然的结果。

    但如果改写成super呢?

    class A(object):
        def __init__(self):
            print "enter A"
            print "leave A"
    
    class B(A):
        def __init__(self):
            print "enter B"
        	super(B,self).__init__()
        	print "leave B"
    
    class C(A):
        def __init__(self):
            print "enter C"
        	super(C,self).__init__()
        	print "leave C"
    
    class D(B,C):
        def __init__(self):
            print "enter D"
        	super(D,self).__init__()
        	print "leave D"
    >>> d=D()
    enter D
    enter B
    enter C
    enter A
    leave A
    leave C
    leave B
    leave D
    

    会发现所有父类ABC只执行了一次,并不像之前那样执行了两次A的初始化。

    然后,又发现一个很奇怪的: 父类的执行是 BCA 的顺序并且是全进入后再统一出去。这是MRO表问题,后面继续讨论。

    MRO 表

    MRO是什么? 可以通过以下方式调出来:

    >>> D.mro() # or d.__class__.mro()  or D.__class__.mro(D) 
    [D, B, C, A, object]
    
    >>> B.mro()
    [B, A, object]
    
    >>> help(D.mro)
    #Docstring:
    #mro() -> list
    #return a type's method resolution order
    #Type:      method_descriptor
    

    MRO就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表 (类继承顺序表去理解也行) 啦.

    这个表有啥用? 首先了解实际super做了啥:

    def super(cls, inst):
        mro = inst.__class__.mro()
        return mro[mro.index(cls) + 1]
    

    换而言之,super方法实际是调用了cls的在MRO表中的下一个类。如果是简单一条线的单继承,那就是父类->父类。但对于多继承,就要遵循MRO表中的顺序了。以上面的D的调用为例:

    d的初始化
    -> D (进入D) super(D,self) 
    -> 父类B (进入B) super(B,self) 
    -> 父类C (进入C) super(C,self) 
    -> 父父类A (进入A)  (退出A) # 如有继续super(A,self)  -> object (停了)
    -> (退出C)
    -> (退出B)
    -> (退出D)
    

    所以, 在MRO表中的超类初始化函数只执行了一次!

    那么, MRO的顺序究竟是怎么定的呢? 这个可以参考官方说明The Python 2.3 Method Resolution Order. 基本就是, 计算出每个类(从父类到子类的顺序)的MRO, 再merge 成一条线. 遵循以下规则:

    在 MRO 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。这个原则包括两点:

    1. 基类永远在派生类后面
    2. 类定义时的继承顺序影响相对顺序.

    如果有以下继承:

       object
         /   
        /      A
       |     /   
      B-1  C-2   D-2
           /    /
         E-1    /
              /
              F
    

    那么MRO是: F -> E -> B -> C -> D -> A -> object

    怎么解释呢?

    根据官方的方法, 是:

    L(O) = O
    L(B) = B O
    L(A) = A O
    L(C) = C A O
    L(D) = D A O
    L(E) = E + merge(L(B),L(C))
         = E + merge(BO,CAO)
         = E + B + merge(O,CAO)
         = E + B + C + merge(O,AO)
         = E + B + C + A + merge(O,O)
         = E B C A O
    L(F) = F + merge(L(E),L(D))
         = F + merge(EBCAO,DAO)
         = F + EBC + merge(AO,DAO)
         = F + EBC + D + merge(AO,AO)
         = F EBC D AO
    

    看起来很复杂,但还是遵循在 MRO 中,基类永远出现在派生类后面,如果有多个基类,基类的相对顺序保持不变。

    reference:

  • 相关阅读:
    CodeForces Gym 100935G Board Game DFS
    CodeForces 493D Vasya and Chess 简单博弈
    CodeForces Gym 100935D Enormous Carpet 快速幂取模
    CodeForces Gym 100935E Pairs
    CodeForces Gym 100935C OCR (水
    CodeForces Gym 100935B Weird Cryptography
    HDU-敌兵布阵
    HDU-Minimum Inversion Number(最小逆序数)
    七月馒头
    非常可乐
  • 原文地址:https://www.cnblogs.com/P3nguin/p/7686902.html
Copyright © 2011-2022 走看看