zoukankan      html  css  js  c++  java
  • 认识python中的super函数

    需求分析

    在类继承中,存在这么一种情况:

    class Human(object):
    	def Move(self):
    		print("我会走路...")
    
    class Man(Human):
    	def Move(self):
    		print("我会跑步...")
    
    man().Move()
    

    输出:

    我会跑步...
    

    人先会走路再会跑步的。如果你想调用Man().Move()时输出以下要怎么做?

    我会走路...
    我会跑步...
    

    这时,其实便需要调用父类Human的Move()方法了。我们知道,在子类中,会覆父类的同名方法。即子类Man里的Move()方法将父类Human中的同名方法Move()覆盖了。要想同时使得两个方法都奏效,可以在子类中主动调用父类中的Move()方法。其实很简单,在类Man中加点东西就行了,如下:

    class Human(object):
    	def Move(self):
    		print("我会走路...")
    
    class Man(Human):
    	def Move(self):
    		Human.Move(self)
    		print("我会跑步...")
    
    Man().Move()
    
    output:
    我会走路...
    我会跑步...
    

    代码中的Human.Move(self)便是在子类Man中主动跟调用父类的方法。我来解释一下这句代码:

    1. Human.Move(self)中Human是类,由类直接调用Move方法,这是未绑定的调用。
    2. Human.Move(self)中的self其实是Man的一个实例Man(),一个个类的实例化是这样的m=Man(),其中,m和Man()都是类Man的实例

    但是,这种方法有一个弊端。比如,如果父类Human吃饱没事干给自己换个名字叫GoodHuman,那么麻烦就来了,你不仅要把Man(Human)改为Man(GoodHuman),还要把Human.Move(self)改为GoodHuman.Move(self)
    当然,这里的例子改起来是没什么可怕。但万一在一些项目里像Human.Move(self)这类型的方法很多的话,你要一个一个改?
    有没有什么办法,使得在父类更改名称时,子类只要改类似Man(Human)一处就好?


    super()出场

    super()的作用是:在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用 super 来实现。

    调用super()地常用格式是:

    class C(B):
      def meth(self, arg):
        super(C, self).meth(arg)
    

    现在,只要将代码改为以下,就不怕父类名称地变化了。(输出结果和上述一样)

    class Human(object):
    	def Move(self):
    		print("我会走路...")
    
    class Man(Human):
    	def Move(self):
    		super(Man, self).Move()
    		print("我会跑步...")
    

    稍微深入

    上面地例子很简单,因为类Man继承地父类已有一个。当继承关系变复杂时,(比如同时继承很多个类,继承的类又继承其他类等等...),便要知道super()是怎么处理继承顺序了。
    看这个例子:

    class Base(object):
        def __init__(self):
            print("enter Base")
            print("leave Base")
     
    class A(Base):
        def __init__(self):
            print("enter A")
            super(A, self).__init__()
            print("leave A")
     
    class B(Base):
        def __init__(self):
            print("enter B")
            super(B, self).__init__()
            print("leave B")
     
    class C(A, B):
        def __init__(self):
            print("enter C")
            super(C, self).__init__()
            print("leave C")
    

    按照前面的思路。我们意淫的输出应该是:

    enter C
    enter A
    enter Base
    leave Base
    leave A
    enter B
    enter Base
    leave Base
    leave B
    leave C
    
    

    但实际输出却是:

    enter C
    enter A
    enter B
    enter Base
    leave Base
    leave B
    leave A
    leave C
    

    我在这里提一个疑问:
    **enter A的下一句为什么不是enter Base而是enter B么? **


    揭开super的面纱

    其实,对于你定义的每一个类,Python 会用方法解析顺序(Method Resolution Order, MRO)计算出一个列表,它代表了类继承的顺序,我们可以使用下面的方式获得某个类的 MRO 列表:

    >>>print(C.mro())
    [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>]
    [Finished in 0.2s]
    

    一个类的 MRO 列表就是合并所有父类的 MRO 列表,并遵循以下三条原则:

    1. 子类永远在父类前面;
    2. 如果有多个父类,会根据它们在列表中的顺序被检查;
    3. 如果对下一个类存在两个合法的选择,选择第一个父类.

    我们查看super函数的代码:

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

    其中,cls 代表类,inst 代表实例,上面的代码做了两件事:

    1. 获取 inst 的 MRO 列表;
    2. 查找 cls 在当前 MRO 列表中的 index, 并返回它的下一个类,即 mro[index + 1]。

    当你使用 super(cls, inst) 时,Python 会在 inst 的 MRO 列表上搜索 cls 的下一个类。
    现在我们来通过一步步追踪来回答上面提出的答案:
    做法很简单:
    1, 修改一下代码:

    class Base(object):
        def __init__(self):
            print("enter Base")
            print("leave Base")
            print(self)
     
    class A(Base):
        def __init__(self):
            print("enter A")
            print(self)
            super(A, self).__init__()
            print("leave A")
     
    class B(Base):
        def __init__(self):
            print("enter B")
            print(self)
            super(B, self).__init__()
            print("leave B")
     
    class C(A, B):
        def __init__(self):
            print("enter C")
            print(self)
            super(C, self).__init__()
            print("leave C")
    
    c = C()
    print(hex(id(c)))
    

    看输出:

    enter C
    <__main__.C object at 0x000001C614CAB7F0>
    enter A
    <__main__.C object at 0x000001C614CAB7F0>
    enter B
    <__main__.C object at 0x000001C614CAB7F0>
    enter Base
    leave Base
    <__main__.C object at 0x000001C614CAB7F0>
    leave B
    leave A
    leave C
    0x1c614cab7f0
    

    在这里塞了这么多代码,就为了说明两件事:

    1. 在A,B,C,Base这四个类中,self都是<__main__.C object at 0x000001C614CAB7F0>这个对象。
    2. <__main__.C object at 0x000001C614CAB7F0>是类C的实例c(c的id输出是0x1c614cab7f0,说明它们是同样的东西)。实例c也就是整个过程的发起者。

    到了这里,就可以回答问题了:
    首先看类C的__init__方法:

    super(C, self).__init__()
    

    这里的 self 是当前 C 的实例,self.__class__.mro() 结果是:

    [__main__.C, __main__.A, __main__.B, __main__.Base, object]
    

    可以看到,C 的下一个类是 A,于是,跳到了 A 的 __init__,这时会打印出 enter A,并执行下面一行代码:

    super(A, self).__init__()
    

    注意,这里的 self 也是当前 C 的实例,MRO 列表跟上面是一样的,搜索 A 在 MRO 中的下一个类,发现是 B,于是,跳到了 B 的__init__,这时会打印出enter B,而不是enter Base。


    总结

    super和父类没有实质性的关联。通过上面分析。我们知道super函数是根据self以及该self对应的方法解析顺序mro来工作的。
    再强调一下,self是一个实例对象,在这里就是实例c(上面代码有一句c=C()),整个过程的发起者。


    版权:保留所有权,转载请注明出处!


  • 相关阅读:
    day47---分组多表查询练习
    day47---数据库进阶知识(二)
    day46---数据库练习
    安全项目(癞蛤蟆病毒)
    pywin32安装步骤
    pip安装报错:error:Microsoft Visual C++ 14.0
    windows下anaconda安装
    数据库常用命令
    MySQL数据库初识
    linux安装MySQL
  • 原文地址:https://www.cnblogs.com/busui/p/7270889.html
Copyright © 2011-2022 走看看