python super()(转载)

一、问题的发现与提出

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

 代码段1:

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 Benter Aleave Aleave B

 

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

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

 代码段2:

class B(C):    # A --> Cdef __init__(self):print "enter B"C.__init__(self) # A --> Cprint "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)

  New in version 2.2.

  从说明来看,可以把类B改写如代码段3:

 代码段3:

class A(object):    # A must be new-style classdef __init__(self):print "enter A"print "leave A"class B(C):     # A --> Cdef __init__(self):print "enter B"super(B, self).__init__()print "leave B"

  尝试执行上面同样的代码,结果一致,但修改的代码只有一处,把代码的维护量降到最低,是一个不错的用法。因此在我们的开发过程中,super关键字被大量使用,而且一直表现良好。

  在我们的印象中,对于super(B, self).__init__()是这样理解的:super(B, self)首先找到B的父类(就是类A),然后把类B的对象self转换为类A的对象(通过某种方式,一直没有考究是什么方式,惭愧),然后“被转换”的类A对象调用自己的__init__函数。考虑到super中只有指明子类的机制,因此,在多继承的类定义中,通常我们保留使用类似代码段1的方法。

  有一天某同事设计了一个相对复杂的类体系结构(我们先不要管这个类体系设计得是否合理,仅把这个例子作为一个题目来研究就好),代码如代码段4:

 代码段4:

 1 class A(object):
 2   def __init__(self):
 3    print "enter A"
 4    print "leave A"
 5 
 6  class B(object):
 7   def __init__(self):
 8    print "enter B"
 9    print "leave B"
10 
11  class C(A):
12   def __init__(self):
13    print "enter C"
14    super(C, self).__init__()
15    print "leave C"
16 
17  class D(A):
18   def __init__(self):
19    print "enter D"
20    super(D, self).__init__()
21    print "leave D"
22  class E(B, C):
23   def __init__(self):
24    print "enter E"
25    B.__init__(self)
26    C.__init__(self)
27    print "leave E"
28 
29  class F(E, D):
30   def __init__(self):
31    print "enter F"
32    E.__init__(self)
33    D.__init__(self)
34    print "leave F"

 

  f = F() result:

enter Fenter Eenter Bleave Benter Center Denter Aleave Aleave Dleave Cleave Eenter Denter Aleave Aleave Dleave F

复制代码
 enter Fenter Eenter Bleave Benter Center Denter Aleave Aleave Dleave Cleave Eenter Denter Aleave Aleave Dleave F
复制代码

  明显地,类A和类D的初始化函数被重复调用了2次,这并不是我们所期望的结果!我们所期望的结果是最多只有类A的初始化函数被调用2次——其实这是多继承的类体系必须面对的问题。我们把代码段4的类体系画出来,如下图:

    object
   |       \
   |        A
   |      / |
   B  C  D
    \   /   |
      E    |
        \   |
          F

  按我们对super的理解,从图中可以看出,在调用类C的初始化函数时,应该是调用类A的初始化函数,但事实上却调用了类D的初始化函数。好一个诡异的问题!

  也就是说,mro中记录了一个类的所有基类的类类型序列。查看mro的记录,发觉包含7个元素,7个类名分别为:

 F E B C D A object

  从而说明了为什么在C.__init__中使用super(C, self).__init__()会调用类D的初始化函数了。 ???

  我们把代码段4改写为:

 代码段9:

class A(object):def __init__(self):print "enter A"super(A, self).__init__()  # newprint "leave A"class B(object):def __init__(self):print "enter B"super(B, self).__init__()  # newprint "leave B"class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D"class E(B, C):def __init__(self):print "enter E"super(E, self).__init__()  # changeprint "leave E"class F(E, D):def __init__(self):print "enter F"super(F, self).__init__()  # changeprint "leave F"

复制代码
class A(object):def __init__(self):print "enter A"super(A, self).__init__()  # newprint "leave A"class B(object):def __init__(self):print "enter B"super(B, self).__init__()  # newprint "leave B"class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D"class E(B, C):def __init__(self):print "enter E"super(E, self).__init__()  # changeprint "leave E"class F(E, D):def __init__(self):print "enter F"super(F, self).__init__()  # changeprint "leave F"
复制代码

f = F() result:

 enter Fenter Eenter Benter Center Denter Aleave Aleave Dleave Cleave Bleave Eleave F

  明显地,F的初始化不仅完成了所有的父类的调用,而且保证了每一个父类的初始化函数只调用一次。

  再看类结构:

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

E-1,D-2是F的父类,其中表示E类在前,即F(E,D)。

所以初始化顺序可以从类结构图来看出 : F->E->B -->C --> D --> A

由于C,D有同一个父类,因此会先初始化D再是A。

三、延续的讨论

  我们再重新看上面的类体系图,如果把每一个类看作图的一个节点,每一个从子类到父类的直接继承关系看作一条有向边,那么该体系图将变为一个有向图。不能发现mro的顺序正好是该有向图的一个拓扑排序序列。

  从而,我们得到了另一个结果——Python是如何去处理多继承。支持多继承的传统的面向对象程序语言(如C++)是通过虚拟继承的方式去实现多继承中父类的构造函数被多次调用的问题,而Python则通过mro的方式去处理。

  但这给我们一个难题:对于提供类体系的编写者来说,他不知道使用者会怎么使用他的类体系,也就是说,不正确的后续类,可能会导致原有类体系的错误,而且这样的错误非常隐蔽的,也难于发现。

四、小结

  1. super并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,
       产生了一个super对象;
  2. super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例;
  3. super(B, self).func的调用并不是用于调用当前类的父类的func函数;
  4. Python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数
       只调用一次(如果每个类都使用super);
  5. 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一
       个父类函数被调用多次。

转载于:https://www.cnblogs.com/xiaoerlang/p/3460939.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/259080.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Swagger+Spring mvc生成Restful接口文档

2019独角兽企业重金招聘Python工程师标准>>> Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端…

JavaScript——变量与基本数据类型

前言 JavaScript中的变量为松散类型,所谓松散类型就是指当一个变量被申明出来就可以保存任意类型的值,就是不像SQL一样申明某个键值为int就只能保存整型数值,申明varchar只能保存字符串。一个变量所保存值的类型也可以改变,这在Ja…

vscode可以打开jupyternotebook吗_刚刚,官方宣布 VS Code 支持 Python 全开发了!

关注Python高校每天早上23:10准时推送北京时间 2019 年 9 月 21 日,PyCon China 2019 在上海举行。在下午的演讲中,来自微软开发工具事业部的资深研发工程师韩骏做了主题为《Python 与 Visual Studio Code 在人工智能应用中的最佳 Azure 实践》的演讲。在…

C++类的内联成员函数应放在哪

今天复习C Primer的时候,看到了关于C类的内联成员函数的放置,应该放在头文件中。那么这到底是为什么 呢?仅仅是一种代码规范问题还是必须这样做呢? 下面我就来讲讲我自己的理解吧。要彻底理解这个问题,首先就要了解下函…

python selenium自动化(三)Chrome Webdriver的兼容

当一个自动化测试被实现在一个浏览器之后,我们会希望我们的测试能够覆盖到尽量多的别的浏览器。通过跨平台的测试来保证我们的程序在多个浏览器下都能正常工作。 在安装了selenium之后,firefox webdriver和IE webdriver就已经是ready to use的了&#xf…

NDK 编译armebai-v7a的非4字节对齐crash Fatal signal 7 (SIGSEGV) 错误解决

一直都是编译armabi的。没有不论什么问题,这个架构是软件模拟浮点运算的。后来看到NDK文档上说armabi-v7a是针对有硬件处理浮点计算的arm cpu的。 于是就改动配置编译armebai-v7a的so文件。 结果是编译没问题。一执行就是crash掉,Fatal signal 7 (SIGSEG…

作业三

作业三 第一章问题:书上写的“Bug的多少可以直接衡量一个软件的开发效率、用户满意度、可靠性和可维护性”,那么一个比较完好的软件中一般大概会出现多少Bug? 第二章问题:现在开始训练写更多的程序能否更早地达到软件工程师的标准&#xff1…

springboot默认数据源如何设置连接数_Spring Boot系列之配置数据库连接池

在实际的应用开发中,与数据库交互通常使用数据库连接池来重用Connection对象,减少资源消耗。Spring Boot 的数据源是自动配置的。在 Spring Boot 2.2.1 版本中,有几种数据源配置可选,它们按照 HikariCP -> Tomcat -> DBCP2 …

使用Qt正则表达式提取全路径的文件名

问题描述: 给定三个全路径,例如 path1"C:/Users/asus/Desktop/nefertiti_4465.obj"; path2"C:/Users/asus/Desktop/nefertiti_4465_k1.txt"; path3"C:/Users/asus/Desktop/nefertiti_4465_k2.txt"; 我希望说明path2和pa…

Beyond Compare 3.3.8 build 16340 + Key

本文摘录自冰点社区:http://forum.z27315.com/topic/14746-beyond-compare-338-build-16340-key/ Download Beyond Compare 3 Current Version: 3.3.8, build 16340, released June 19, 2013 Windows 版本 Windows Standard and Pro EditionsEnglish version 5800k…

hdu 1198 Farm Irrigation

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid1198 题目大意: 有一大块土地需要浇水,这块土地由很多的小块土地(有十一种)组成,小块土地上有水沟,问至少需要建几个井,才能灌…

strcpy_s、sptintf_s与strcat_s的使用

strcpy_s、sptintf_s与strcat_s是strcpy、sptintf与strcat的安全版本,均是通过指定缓冲区长度来避免存在的溢出风险。 strcpy_s 与strcpy strcpy_s和strcpy函数的功能几乎是一样的。strcpy函数,就象gets函数一样,它没有方法来保证有效的缓冲…

小米一键上锁工具_小米首款高端全自动智能锁火热预售中,一触开启全自动时代...

近些年,随着科技的发展,人工智能逐渐走入大众视野。人类社会也正从信息时代向“智能时代”过渡,在整个过程中智能家居领域的蓬勃发展可谓当仁不让,一直备受用户瞩目。智能锁作为家的第一道守护防线,家庭物联网入口的关…

Eigen+suitesparse for windows 安装

Eigen是著名的C矩阵运算库,提供了许多矩阵运算的接口,主要包括两大部分,一部分是稠密矩阵,另一部分是稀疏矩阵。Eigen以源码形式提供给大家,用的时候,只要将源码包含在项目的包含路径上,具体安装…

软件盘控制的问题

2019独角兽企业重金招聘Python工程师标准>>> 在全屏模式或者是沉寝室标题栏 方案一:全屏模式 1.软键盘被EditText遮挡住了,如果说EditText被嵌套在有滑动的视图中,采取的方式是: activity中设置此属性 android:windowSoftInputMode"…

python语言学习零基础教学视频_Python告白小白视频教程(零基础入门)

1 Python编程基础入门篇通过本次课程的学习,我们每个人都可以进入python世界里,从简单到高级,让人人都能学会python,我们在学习的时候,python让我们的运维变得更有乐趣,让我们的运维更加的高大上&#xff0…

SQL 快速入门2.1

MySQL top(MySQL limit)语法 SELECT column_name(s) FROM table_name LIMIT number 例子 SELECT * FROM Persons LIMIT 5 SQL LIKE 操作符 SQL LIKE 操作符语法 SELECT column_name(s) FROM table_name WHERE column_name LIKE pattern 原始的表 (用在例…

sencha touch 入门系列 (一)sencha touch 简介

参考链接:http://mobile.51cto.com/others-278381.htm Sencha touch 是基于JavaScript编写的Ajax框架ExtJS,将现有的ExtJS整合JQTouch、Raphaël库,推出适用于最前沿Touch Web的移动应用开发框架,该框架是世界上第一个 基于HTML5的Mobile App框架…

求二叉树的深度和宽度

// 求二叉树的深度和宽度.cpp : 定义控制台应用程序的入口点。 <pre name"code" class"cpp">#include <iostream> #include <queue> using namespace std;struct BTNode {char m_value;BTNode *m_left;BTNode *m_right; };//先序创建二叉…

汉堡包

在我们结对的这些天里&#xff0c;我清晰的感受到同伴对我的帮助&#xff0c;每当我有不懂的时候她都会积极的帮助我&#xff0c;也会听取我的意见积极配合我&#xff0c;在我懒惰的时候也能够提醒督促我&#xff0c;我想这些只有结对时才能体会到。我们都知道&#xff0c;结对…