美文网首页
super的理解和使用

super的理解和使用

作者: MononokeHime | 来源:发表于2018-07-01 14:56 被阅读0次

转自python2 为解决钻石继承难题,super()的使用

写在最前面:
1.super是一个类,返回的是一个 proxy对象,目的是可以让你访问父类的一些特殊方法
2.你得按照父类对应的特殊方法去传递参数,父类没有的参数就不要乱传
3.不要一说到 super 就想到父类!super 指的是 MRO 中的下一个类!

另一篇比较好的文章:理解 Python super

1. python2 子类调用父类函数成员有2种方法:普通方法和super()方法

假设Base是基类

class Base(object):
      def __init__(self):
           print “Base init”
  • 则普通方法如下
class Leaf(Base):
       def __init__(self):
              Base.__init__(self)
              print “Leaf init”
  • super()方法如下
class Leaf(Base):
       def __init__(self):
              super(Leaf, self).__init__()
              print “Leaf init”

两种方法,效果一致:

>>> leaf = Leaf()
Base init
Leaf init

2.使用普通方法的缺陷

使用普通方法遇到钻石继承时,会遇到难题(孙子辈子类继承自两个叔叔级的父类,爷爷辈的类会初始化两次)。

img

如果使用普通方法时,Leaf 类同时调用Medium1,Medium2时会初始化两次Base类。

class Base(object):
    def __init__(self):
        print “Base init”
class Medium1(Base):
    def __init__(self):
        Base.__init__(self)
        print “Medium1 init”
class Medium2(Base):
    def __init__(self):
        Base.__init__(self)
        print “Medium2 init”
class Leaf(Medium1, Medium2):
    def __init__(self):
        Medium1.__init__(self)
        Medium2.__init__(self)
        print “Leaf init”

当生成Leaf对象时,Base 会被初始化两次,结果如下:

>>> leaf = Leaf()
Base init
Medium1 init
Base init
Medium2 init
Leaf init

3.各语言的解决方法

钻石继承中,父类被多次初始化是个非常难缠的问题,我们来看看其他各个语言是如何解决这个问题的:

3.1. C++

C++使用虚拟继承来解决钻石继承问题。

Medium1和Medium2虚拟继承Base。当生成Leaf对象时,Medium1和Medium2并不会自动调用虚拟基类Base的构造函数,而需要由Leaf的构造函数显式调用Base的构造函数。

3.2. Java

Java禁止使用多继承。

Java使用单继承+接口实现的方式来替代多继承,避免了钻石继承产生的各种问题。

3.3. Ruby

Ruby禁止使用多继承。

Ruby和Java一样只支持单继承,但它对多继承的替代方式和Java不同。Ruby使用Mixin的方式来替代,在当前类中mixin入其他模块,来做到代码的组装效果。

3.4. Python

Python和C++一样,支持多继承的语法。但Python的解决思路和C++完全不一样,Python是的用就是super

同样的继承 用super 重写 ,生成Leaf对象试一下。

>>> leaf = Leaf()
Base init
Medium2 init
Medium1 init
Leaf init

相比于普通写法,super完美解决钻石继承问题,并且代码更为简洁。

4. super的内核:mro

那super是如何解决砖石继承问题的呢?要理解super的原理,就要先了解mro。mro是method resolution order的缩写,表示了类继承体系中的成员解析顺序。

在python中,每个类都有一个mro的类方法。我们来看一下钻石继承中,Leaf类的mro是什么样子的:

>>> Leaf.mro()

[<class '__main__.Leaf'>, <class '__main__.Medium1'>, <class '__main__.Medium2'>, <class '__main__.Base'>, <type 'object'>]

可以看到mro方法返回的是一个祖先类的列表。Leaf的每个祖先都在其中出现一次,这也是super在父类中查找成员的顺序。

通过mro,python巧妙地将多继承的图结构,转变为list的顺序结构。super在继承体系中向上的查找过程,变成了在mro中向右的线性查找过程,任何类都只会被处理一次。

通过这个方法,python解决了多继承中的2大难题:

  1. 查找顺序问题。从Leaf的mro顺序可以看出,如果Leaf类通过super来访问父类成员,那么Medium1的成员会在Medium2之前被首先访问到。如果Medium1和Medium2都没有找到,最后再到Base中查找。
  2. 钻石继承的多次初始化问题。在mro的list中,Base类只出现了一次。事实上任何类都只会在mro list中出现一次。这就确保了super向上调用的过程中,任何祖先类的方法都只会被执行一次。

至于mro的生成算法,可以参考这篇wiki:C3 linearization

5. super的具体用法

我们首先来看一下python中的super定义

    def __init__(self, type1=None, type2=None): # known special case of super.__init__
        """
        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)
        
        # (copied from class doc)
        """

下面详细解析super中的下面两种用法

5.1. super(type, obj)
补充:super()等价于super(__class__, self)

当我们在Leaf的__init__中写这样的super时:

class Leaf(Medium1, Medium2):
       def __init__(self):
              super(Leaf, self).__init__()
              print “Leaf init”

super(Leaf, self).__init__()的意思是说:

  1. 获取self所属类的mro, 也就是[Leaf, Medium1, Medium2, Base]
  2. 从mro中Leaf右边的一个类开始,依次寻找__init__函数。这里是从Medium1开始寻找
  3. 一旦找到,就把找到的__init__函数绑定到self对象,并返回

从这个执行流程可以看到,如果我们不想调用Medium1的__init__,而想要调用Medium2的__init__,那么super应该写成:super(Medium1, self)__init__()

案例理解:

class Root(object):
    def __init__(self):
        print("this is Root")

class B(Root):
    def __init__(self):
        print("enter B")
        # print(self)  # this will print <__main__.D object at 0x...>
        super(B, self).__init__()
        print("leave B")

class C(Root):
    def __init__(self):
        print("enter C")
        super(C, self).__init__()
        print("leave C")

class D(B, C):
    pass

d = D()
print(d.__class__.__mro__)

输出

enter B
enter C
this is Root
leave C
leave B
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.Root'>, <type 'object'>)

为什么 enter B 下一句是 enter C 而不是 this is Root(如果认为 super 代表“调用父类的方法”,会想当然的认为下一句应该是this is Root)?。流程如下,在 B 的 __init__ 函数中:

super(B, self).__init__()

首先,我们获取 self.__class__.__mro__,注意这里的 self 是 D 的 instance 而不是 B 的

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.Root'>, <type 'object'>)

然后,通过 B 来定位 MRO 中的 index,并找到下一个。显然 B 的下一个是 C。于是,我们调用 C 的 __init__,打出 enter C。

5.2. super(type, type2)

在Base,Medium1,Medium2, Leaf类中分别写入__new__()方法,并使用super():

class Base(object):
    def __new__(self):
        print "Base new"

class Medium1(Base):
    def __new__(cls):
        super(Medium1,cls).__new__(cls)
        print "Medium1 new"

class Medium2(Base):
    def __new__(cls):
        super(Medium2,cls).__new__(cls)
        print "Medium2 new"

class Leaf(Medium1, Medium2):
       def __new__(cls):
              obj = super(Leaf, cls).__new__(cls)
              print "Leaf new"
              return obj

super(Leaf, cls).__new__(cls)的意思是说:

  1. 获取cls这个类的mro,这里也是[Leaf, Medium1, Medium2, Base]
  2. 从mro中Leaf右边的一个类开始,依次寻找__new__函数
  3. 一旦找到,就返回“非绑定”的__new__函数

由于返回的是非绑定的函数对象,因此调用时不能省略函数的第一个参数。这也是这里调用__new__时,需要传入参数cls的原因

同样的,如果我们想从某个mro的某个位置开始查找,只需要修改super的第一个参数就行。
一个很好的应用就是单例模式:

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls,'_instance'):
            cls._instance = super(Singleton,cls).__new__(cls,*args,**kwargs)
        return cls._instance

singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1 is singleton2)

补充:Python3按照广度优先

class C:
    def f(self):
        print('C')
        super(C, self).f()

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

class B:
    def f(self):
        print('B')

class Foo(A,B):
    pass

foo = Foo()
foo.f()

输出

A
C
B

相关文章

  • super的理解和使用

    转自python2 为解决钻石继承难题,super()的使用 写在最前面:1.super是一个类,返回的是一个 p...

  • super

    super关键字的使用 super理解为:父类的 super可以用来调用:属性、方法、构造器 super的使用:(...

  • 怎么理解Python类中的super函数

    前言 在Python类的继承中,经常能看到super函数的存在,那super函数主要的作用,以及如何理解和使用好这...

  • Java 基础 20 super关键字以及继承中的方法重写

    1.1 super 关键字的概述和使用 1.1.1super 关键字的概述 super的用法和this很像this...

  • self和super的理解

    下面代码输出的是什么: 答案: 都是Son。 self 是类的隐藏参数,指向当前调用方法的这个类的实例。而 sup...

  • self和super的理解

    下面的代码输出什么?@implementation Son : Father - (id)init { self ...

  • self和super的理解

    下面的代码输出什么? @implementationSon:Father -(id)init { self=[su...

  • 理解与使用

    1、关于此含义的总结描述: ? extends T与?super T两者用于泛型对象的读取和插入操作:? exte...

  • reactES6写法

    注意: super()是为了使用this,必须在使用this之前声明super(); super(props)这个...

  • Java 泛型 <? super T> 中 supe

    Java 泛型 中 super 怎么 理解?与 extends 有何不同? super只能...

网友评论

      本文标题:super的理解和使用

      本文链接:https://www.haomeiwen.com/subject/engcuftx.html