from:https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html

8.7 调用父类方法

问题

你想在子类中调用父类的某个已经被覆盖的方法。

解决方案

为了调用父类(超类)的一个方法,可以使用 super() 函数,比如:

class A:
def spam(self):
print('A.spam') class B(A):
def spam(self):
print('B.spam')
super().spam() # Call parent spam()

super() 函数的一个常见用法是在 __init__() 方法中确保父类被正确的初始化了:

class A:
def __init__(self):
self.x = 0 class B(A):
def __init__(self):
super().__init__()
self.y = 1

super() 的另外一个常见用法出现在覆盖Python特殊方法的代码中,比如:

class Proxy:
def __init__(self, obj):
self._obj = obj # Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name) # Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # Call original __setattr__
else:
setattr(self._obj, name, value)

在上面代码中,__setattr__() 的实现包含一个名字检查。 如果某个属性名以下划线(_)开头,就通过 super() 调用原始的 __setattr__() , 否则的话就委派给内部的代理对象 self._obj 去处理。 这看上去有点意思,因为就算没有显式的指明某个类的父类, super() 仍然可以有效的工作。

讨论

实际上,大家对于在Python中如何正确使用 super() 函数普遍知之甚少。 你有时候会看到像下面这样直接调用父类的一个方法:

class Base:
def __init__(self):
print('Base.__init__') class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__')

尽管对于大部分代码而言这么做没什么问题,但是在更复杂的涉及到多继承的代码中就有可能导致很奇怪的问题发生。 比如,考虑如下的情况:

class Base:
def __init__(self):
print('Base.__init__') class A(Base):
def __init__(self):
Base.__init__(self)
print('A.__init__') class B(Base):
def __init__(self):
Base.__init__(self)
print('B.__init__') class C(A,B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print('C.__init__')

如果你运行这段代码就会发现 Base.__init__() 被调用两次,如下所示:

>>> c = C()
Base.__init__
A.__init__
Base.__init__
B.__init__
C.__init__
>>>

可能两次调用 Base.__init__() 没什么坏处,但有时候却不是。 另一方面,假设你在代码中换成使用 super() ,结果就很完美了:

class Base:
def __init__(self):
print('Base.__init__') class A(Base):
def __init__(self):
super().__init__()
print('A.__init__') class B(Base):
def __init__(self):
super().__init__()
print('B.__init__') class C(A,B):
def __init__(self):
super().__init__() # Only one call to super() here
print('C.__init__')

运行这个新版本后,你会发现每个 __init__() 方法只会被调用一次了:

>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>>

为了弄清它的原理,我们需要花点时间解释下Python是如何实现继承的。 对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。例如:

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>>

为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

而这个MRO列表的构造是通过一个C3线性化算法来实现的。 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

老实说,你所要知道的就是MRO列表中的类顺序会让你定义的任意类层级关系变得有意义。

当你使用 super() 函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super() 并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。 这也是为什么在第二个例子中你不会调用两次 Base.__init__() 的原因。

super() 有个令人吃惊的地方是它并不一定去查找某个类在MRO中下一个直接父类, 你甚至可以在一个没有直接父类的类中使用它。例如,考虑如下这个类:

class A:
def spam(self):
print('A.spam')
super().spam()

如果你试着直接使用这个类就会出错:

>>> a = A()
>>> a.spam()
A.spam
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in spam
AttributeError: 'super' object has no attribute 'spam'
>>>

但是,如果你使用多继承的话看看会发生什么:

>>> class B:
... def spam(self):
... print('B.spam')
...
>>> class C(A,B):
... pass
...
>>> c = C()
>>> c.spam()
A.spam
B.spam
>>>

你可以看到在类A中使用 super().spam() 实际上调用的是跟类A毫无关系的类B中的 spam() 方法。 这个用类C的MRO列表就可以完全解释清楚了:

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class 'object'>)
>>>

在定义混入类的时候这样使用 super() 是很普遍的。可以参考8.13和8.18小节。

然而,由于 super() 可能会调用不是你想要的方法,你应该遵循一些通用原则。 首先,确保在继承体系中所有相同名字的方法拥有可兼容的参数签名(比如相同的参数个数和参数名称)。 这样可以确保 super() 调用一个非直接父类方法时不会出错。 其次,最好确保最顶层的类提供了这个方法的实现,这样的话在MRO上面的查找链肯定可以找到某个确定的方法。

在Python社区中对于 super() 的使用有时候会引来一些争议。 尽管如此,如果一切顺利的话,你应该在你最新代码中使用它。 Raymond Hettinger为此写了一篇非常好的文章 “Python’s super() Considered Super!” , 通过大量的例子向我们解释了为什么 super() 是极好的。

python3 推荐使用super调用base类方法的更多相关文章

  1. Unity调用Android类方法

    Unity调用Android类方法 1.  添加Unity的classes.jar文件 创建一个Android工程AndroidUnityDemo. 由于Unity的版本不同,直接在Unity安装包文 ...

  2. java构造函数是否可继承,以及子类构造函数可否不使用super调用超类构造函数

    问题一:java的构造函数能否被继承? 笔者初学java看的一本书说:“java的子类自然的继承其超类的“非private成员”. 通常java的构造函数被设置为public的(若你不写构造函数,ja ...

  3. 继承内部类时使用外部类对象.super()调用内部类的构造方法

    问题简介   今天在看<Java编程思想>的时候,看到了一个很特殊的语法,懵逼了半天--一个派生类继承自一个内部类,想要创建这个派生类的对象,首先得创建其父类的对象,也就是这个内部类,而调 ...

  4. Python3中的super()函数详解

    关于Python3中的super()函数 我们都知道,在Python3中子类在继承父类的时候,当子类中的方法与父类中的方法重名时,子类中的方法会覆盖父类中的方法, 那么,如果我们想实现同时调用父类和子 ...

  5. super()调用父类构造方法

    super()表示调用父类中的构造方法 1.子类继承父类,子类的构造方法的第一行,系统会默认编写super(),在调用子类的构造方法时,先调用父类的无参数构造方法 2.如果父类中只有有参数构造方法,那 ...

  6. 使用super调用父类的构造方法

    package com.bjpowernode.t02inheritance.c09; /* * 使用super调用父类的构造方法 */public class TestSuper02 { publi ...

  7. python使用super()调用父类的方法

    如果要在子类中引用父类的方法,但是又需要添加一些子类所特有的内容,可通过类名.方法()和super()来调用父类的方法,再个性化子类的对应函数. 直接使用类名.方法()来调用时,还是需要传入self为 ...

  8. 关于父类私有属性在子类构造函数中super调用的解释

    package test; public class Car { private int carMoney; //汽车租金 private String carName; //汽车名字 private ...

  9. Python3.x:pyodbc调用sybase的存储过程

    Python3.x:pyodbc调用sybase的存储过程 示例代码 # python3 # author lizm # datetime 2018-03-02 17:00:00 # -*- codi ...

随机推荐

  1. [LeetCode] 170. Two Sum III - Data structure design 两数之和之三 - 数据结构设计

    Design and implement a TwoSum class. It should support the following operations:add and find. add - ...

  2. [LeetCode] 243. Shortest Word Distance 最短单词距离

    Given a list of words and two words word1 and word2, return the shortest distance between these two ...

  3. CentOS 7.5二进制部署Kubernetes1.12(加密通信)(五)

    一.安装方式介绍 1.yum 安装 目前CentOS官方已经把Kubernetes源放入到自己的默认 extras 仓库里面,使用 yum 安装,好处是简单,坏处也很明显,需要官方更新 yum 源才能 ...

  4. linux U盘 硬盘 unable to mount

    转自aaa小菜鸡 出现原因: 上次文件没拷完就拔了U盘 参考: 解决Ubuntu挂载U盘失败的解决方法 Linux下无法挂载U盘 解决: 1.sudo fdisk -l列出当前系统下的文件设备 2.s ...

  5. [转帖]keepalived工作原理和配置、使用

    keepalived工作原理和配置.使用 https://www.iteye.com/blog/aoyouzi-2288124 keepalived是什么 keepalived是集群管理中保证集群高可 ...

  6. 爬虫请求库之requests库

    一.介绍 介绍:使用requests可以模拟浏览器的请求,比之前的urllib库使用更加方便 注意:requests库发送请求将网页内容下载下来之后,并不会执行js代码,这需要我们自己分析目标站点然后 ...

  7. Github-Q&A 常见错误排查(持续更新)

    Q1:提交Git时出现"remote origin already exists"提示?   关联本地与网上仓库的时候,可能会出现此错误,这是因为已经存在已关联的仓库.通过如下方法 ...

  8. 用GDB调试程序(四)

    查看栈信息————— 当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的.当你的程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中.你可以用GDB命令 ...

  9. xorm-Iterate/Count/Rows方法实例

    package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/ ...

  10. Microsoft.AspNet.Identity 自定义使用现有的表—登录实现,aspnet.identity

    Microsoft.AspNet.Identity是微软新引入的一种membership框架,也是微软Owin标准的一个实现.Microsoft.AspNet.Identity.EntityFrame ...