基类与继承 / Base Class and Inheritance Class


面向对象的特性使得 Python 中不可避免地需要使用到类和类的继承,类的继承可以使得代码很好的被重用。下面以一些代码示例说明类的继承如何使用。

继承一个基类


首先,定义一个基类 Animal,在初始化中设定一个基本属性以及物种信息,并设置其具有 eat 的能力(self.eat 为 True)。此处还重载了魔术方法 __getattr__,当搜索的属性不存在时返回 False(即不具备该能力),最后定义一个 show 函数来显示当前 species 信息。

 # ------- Basic inheritance --------
class Animal:
def __init__(self):
self.base = "Creature"
self.species = "Animal"
self.eat = True def __getattr__(self, item):
return False def show(self):
print("This is %s." % self.species)

接着定义一个 Bird 类,继承自 Animal ,在 Bird 的初始化函数中,首先对基类进行初始化,然后设置 species 属性,以及 fly 为True(代表具有 fly 的能力),并对 Bird 定义了 show 函数。同样定义 Fish 类,设置 species 信息以及 swim 为 True,且不定义 show 函数。

 # Note: Do not mix super and Object.__init__
class Bird(Animal):
def __init__(self):
Animal.__init__(self)
self.species = "Bird"
self.fly = True def show(self):
print("This is %s from %s." % (self.species, self.base)) class Fish(Animal):
def __init__(self):
Animal.__init__(self)
self.species = "Fish"
self.swim = True

最后,对两个类分别进行实例化,并调用各自的 show 函数,

 b = Bird()
f = Fish()
b.show()
f.show()

输出的结果如下,

This is Bird from Creature.
This is Fish.

从最终的输出结果可以看到,

对于 Bird 类,并没有对 base 属性进行定义,但却拥有 base 属性,即这一属性从基类 Animal 中继承得到,

而对于 Fish 类,并未定义 show 方法,但是却在实例中可以使用 show 方法。同样这一方法也是继承自基类 Animal。

这两个简单的示例中通过继承从而避免了重复的代码和定义。

多继承


前面的例子中使用的基类是唯一的,当需要从多个基类中继承时,则会涉及到多继承问题。下面的代码给出一个多继承问题的示例。

首先导入之前定义的两个类 Bird 和 Fish 作为基类,然后基于这两个类派生出子类 Duck 和 Goose,其中 Goose 中重载了初始化函数,利用 super 初始化 Goose 的基类。此时,我们希望 Duck 和 Goose 能够继承 Bird 和 Fish 的两个特性,fly 和 swim。

Note: 此处为示例代码,切勿混用 super 和类的初始化函数。

 from inheritance_base import Bird, Fish
# ------- Multi inheritance --------
class Duck(Bird, Fish): pass class Goose(Bird, Fish):
def __init__(self):
super(Goose, self).__init__() d = Duck()
g = Goose()
print("I am %s, I can fly: %s" % (d.species, d.fly))
print("I am %s, I can swim: %s" % (d.species, d.swim))
print("I am %s, I can fly: %s" % (g.species, g.fly))
print("I am %s, I can swim: %s" % (g.species, g.swim))

最终的运行结果如下,

I am Bird, I can fly: True
I am Bird, I can swim: False
I am Bird, I can fly: True
I am Bird, I can swim: False

从运行的结果中看到,最后的输出却不是我们所期望的,无论是 Duck 的实例还是 Goose 的实例都不具备 swim 的属性,也就是说,Fish 的初始化并未被执行。

其原因在于,Duck 中并未定义初始化函数,因此会在父类中搜索,从而调用到 Bird 的初始化函数,所以最终显示 species 属性为 Bird,而 swim 为 False。而在 Goose 中,使用 super 也未能成功继承所有属性,这是由于 super 会依照 MRO 顺序进行搜索,使用搜索到的第一个类的对应方法。

为实现所需要的功能,在这里重新定义一个 Duck 类,重载初始化函数,在初始化函数中分别调用两个基类的初始化函数。

 class Duck(Bird, Fish):
def __init__(self):
Bird.__init__(self)
Fish.__init__(self) d = Duck()
print("I am %s, I can fly: %s" % (d.species, d.fly))
print("I am %s, I can swim: %s" % (d.species, d.swim))

最终输出结果可以看到,species 属性被覆盖了最终显示为 Fish,而 Goose 也具备了 fly 和 swim 两个属性。

I am Fish, I can fly: True
I am Fish, I can swim: True

但这种继承依旧会引起一些问题。

菱形 / 钻石继承


前面的多继承使用直接调用父类初始化函数进行,表面上能够满足多继承的需求,但是仍然存在一些问题,下面以一个菱形/钻石继承来说明这一问题是如何存在的。

首先是定义 A、B、C、D 四个类,这四个类大致为菱形继承的形式。

"""
A
/\
/ \
/ \
B C
\ /
\ /
\/
D
"""

然后以 no-super 和 super 两种方式来实现这一继承,
在 no super 中不使用 super,直接使用调用父类初始化函数的方法来完成初始化工作,具体方式如下,

 # ------- no super ----------
class A(object):
def __init__(self):
object.__init__(self)
print("This is A init.") class B(A):
def __init__(self):
A.__init__(self)
print("This is B init.") class C(A):
def __init__(self):
A.__init__(self)
print("This is C init.") class D(B, C):
def __init__(self):
B.__init__(self)
C.__init__(self)
print("This is D init.") d = D()

输出结果如下,从输出中可以看出,这种方式存在一个问题,那就是对 B 和 C 的基类 A 进行了多次的初始化。这可能会造成一些不期望的结果。

This is A init.
This is B init.
This is A init.
This is C init.
This is D init.

为了避免这种现象,可以使用 super 来完成继承初始化。

 # ------- super ------------
class A(object):
def __init__(self):
super(A, self).__init__()
print("This is A init.") class B(A):
def __init__(self):
super(B, self).__init__()
print("This is B init.") class C(A):
def __init__(self):
super(C, self).__init__()
print("This is C init.") class D(B, C):
def __init__(self):
super(D, self).__init__()
print("This is D init.") d = D()

使用super的初始化结果显示如下,可以看到,此时能够实现对 A 的初始化只进行一次。这是由于 super 中实现了 MRO 的搜索算法。

This is A init.
This is C init.
This is B init.
This is D init.

相关阅读


1. 魔术方法 __getattr__

2. super

3. MRO 顺序

Python的程序结构[2] -> 类/Class[1] -> 基类与继承的更多相关文章

  1. Python+Selenium框架设计之框架内封装基类和实现POM

    原文地址https://blog.csdn.net/u011541946/article/details/70269965 作者:Anthony_tester 来源:CSDN    博客地址https ...

  2. 请高手解释这个C#程序,其中ServiceBase是windows服务基类,SmsService是

    请高手解释这个C#程序,其中ServiceBase是windows服务基类,SmsService是 ServiceBase的子类. static void Main() { ServiceBase[] ...

  3. 派生类地址比基类地址少4(子类与基类指针强行转换的时候,值居然会发生变化,不知道Delphi BCB是不是也这样) good

    大家对虚表并不陌生,都知道每个含有虚函数的类对象都有1个虚指针,但是在现实使用中,却总是因为这而调试半天,才发现原来是虚指针惹的祸.我这几天在调试代码时候也中招了,我的问题是这样的,如下图,CTree ...

  4. 【Android进阶】为什么要创建Activity基类以及Activity基类中一般有哪些方法

    现在也算是刚刚基本完成了自己的第一个商业项目,在开发的过程中,参考了不少人的代码风格,然而随着工作经验的积累,终于开始慢慢的了解到抽象思想在面向对象编程中的重要性,这一篇简单的介绍一下我的一点收获. ...

  5. 派生类地址比基类地址少4(CDerived对象的起始地址存放的是虚表指针vptr,也就是子类的第一项内容。接下来的是基类的成员变量,接下来再是自身的成员变量)

    大家对虚表并不陌生,都知道每个含有虚函数的类对象都有1个虚指针,但是在现实使用中,却总是因为这而调试半天,才发现原来是虚指针惹的祸.我这几天在调试代码时候也中招了,我的问题是这样的,如下图,CTree ...

  6. 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)

    [源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...

  7. C++ - 派生类访问模板基类(templatized base class)命名

    派生类访问模板基类(templatized base class)命名 本文地址: http://blog.csdn.net/caroline_wendy/article/details/239936 ...

  8. laravel5.8笔记五:基类控制器和基类模型

    建立基类的目的就是为了方便继承.比如:Admin模块访问,是否登陆.检测登陆可以写到基类里面 控制器基类 原始基类:app\Http\Controllers\Controller.php,我们下面要做 ...

  9. C++ - 派生类强制转换为基类

    派生类强制转换为基类 本文地址: http://blog.csdn.net/caroline_wendy/article/details/24268821 在多态的使用时, 派生类的指针或引用能够转换 ...

  10. C#在派生类中调用基类成员

    一.在派生类中调用基类成员 在C#的派生类中,我们可以使用base关键字调用基类中的公有或者受保护成员.这些成员只能是构造函数.实例方法或者实例属性. base关键字调用基类成员的语法格式如下: ba ...

随机推荐

  1. Pascal编写的蠕虫病毒,凌盟提供,Chaobs转载

    { Happy Birthday (c) 1998 WoRmI don't take responsibility for any damage caused by this virus.It was ...

  2. Django笔记 —— 入门简介

    最近在学习Django,打算玩玩网页后台方面的东西,因为一直很好奇但却没怎么接触过.Django对我来说是一个全新的内容,思路想来也是全新的,或许并不能写得很明白,所以大家就凑合着看吧- 本篇笔记(其 ...

  3. 自动化测试(三)如何用python写个双色球

    写一个程序,输入N就产生N条双色球号码 红球  6     01-33 蓝球  1     01-16 产生的双色球号码不能重复,写到一个文件里面,每一行是一条 红球: 01 03 05 07 08  ...

  4. HTTP响应码

    更详细的内容参考:https://baike.baidu.com/item/HTTP状态码/5053660?fr=aladdin 转载CSDN作者:ddhsea 的文章 https://blog.cs ...

  5. Windows批处理中获取日期和时间

    编写Windows bat 批处理时经常会需要使用到日期和时间作为文件名,所以是非常重要的. 如何获取日期呢? 格式: %date% 结果: -- 如何获取时间呢? 格式: %time% 结果: :: ...

  6. 转载css3 图片圆形显示 如何CSS将正方形图片显示为圆形图片布局

    转载  原文:http://www.divcss5.com/wenji/w732.shtml 原本不是圆形图片,通过CSS样式布局实现成圆形图片,首先图片必须为正方形. 二.使用布局技术   -    ...

  7. springmvc项目搭建三-添加前端框架

    这几年前端框架发展可以说非常迅猛了...实际项目中也用到了几个,easyui相对来讲,算是我第一个接触的前端框架了,用的时候感觉很方便,省了很多代码量,一个好的前端框架可以为你省去很多精力在前端布局上 ...

  8. BI商业智能培训系列——(三)SSAS入门

    简介:     一个客户端工具,提供了设计.创建和管理来自数据仓库的多维数据集的功能,还提供对OLAP数据客户端访问功能. 解决方案如下: 相关概念: 维度: 维度可以理解为划分依据,简单的说就是看问 ...

  9. luajit的字节码

    http://blog.csdn.net/zzz3265/article/details/41146569 这里写出了luajit的字节码

  10. 【bzoj2732】[HNOI2012]射箭 二分+半平面交

    题目描述 给出二维平面上n个与y轴平行的线段,求最大的k,使得存在一条形如$y=ax^2+bx(a<0,b>0)$的抛物线与前k条线段均有公共点 输入 输入文件第一行是一个正整数N,表示一 ...