• Python的程序结构[2] -> 类/Class[1] -> 基类与继承


    基类与继承 / Base Class and Inheritance Class


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

    继承一个基类


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

     1 # ------- Basic inheritance --------
     2 class Animal:
     3     def __init__(self):
     4         self.base = "Creature"
     5         self.species = "Animal"
     6         self.eat = True
     7 
     8     def __getattr__(self, item):
     9         return False
    10 
    11     def show(self):
    12         print("This is %s." % self.species)

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

     1 # Note: Do not mix super and Object.__init__
     2 class Bird(Animal):
     3     def __init__(self):
     4         Animal.__init__(self)
     5         self.species = "Bird"
     6         self.fly = True
     7 
     8     def show(self):
     9         print("This is %s from %s." % (self.species, self.base))
    10 
    11 class Fish(Animal):
    12     def __init__(self):
    13         Animal.__init__(self)
    14         self.species = "Fish"
    15         self.swim = True

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

    1 b = Bird()
    2 f = Fish()
    3 b.show()
    4 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 和类的初始化函数。

     1 from inheritance_base import Bird, Fish
     2 # ------- Multi inheritance --------
     3 class Duck(Bird, Fish): pass
     4 
     5 class Goose(Bird, Fish):
     6     def __init__(self):
     7         super(Goose, self).__init__()
     8 
     9 d = Duck()
    10 g = Goose()
    11 print("I am %s, I can fly: %s" % (d.species, d.fly))
    12 print("I am %s, I can swim: %s" % (d.species, d.swim))
    13 print("I am %s, I can fly: %s" % (g.species, g.fly))
    14 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 类,重载初始化函数,在初始化函数中分别调用两个基类的初始化函数。

    1 class Duck(Bird, Fish):
    2     def __init__(self):
    3         Bird.__init__(self)
    4         Fish.__init__(self)
    5 
    6 d = Duck()
    7 print("I am %s, I can fly: %s" % (d.species, d.fly))
    8 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,直接使用调用父类初始化函数的方法来完成初始化工作,具体方式如下,

     1 # ------- no super ----------
     2 class A(object):
     3     def __init__(self):
     4         object.__init__(self)
     5         print("This is A init.")
     6 
     7 class B(A):
     8     def __init__(self):
     9         A.__init__(self)
    10         print("This is B init.")
    11 
    12 class C(A):
    13     def __init__(self):
    14         A.__init__(self)
    15         print("This is C init.")
    16         
    17 class D(B, C):
    18     def __init__(self):
    19         B.__init__(self)
    20         C.__init__(self)
    21         print("This is D init.")
    22 
    23 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 来完成继承初始化。

     1 # ------- super ------------
     2 class A(object):
     3     def __init__(self):
     4         super(A, self).__init__()
     5         print("This is A init.")
     6 
     7 class B(A):
     8     def __init__(self):
     9         super(B, self).__init__()
    10         print("This is B init.")
    11 
    12 class C(A):
    13     def __init__(self):
    14         super(C, self).__init__()
    15         print("This is C init.")
    16         
    17 class D(B, C):
    18     def __init__(self):
    19         super(D, self).__init__()
    20         print("This is D init.")
    21 
    22 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 顺序

  • 相关阅读:
    css3背景色过渡
    HttpUtility.UrlEncode与Server.UrlEncode()转码区别
    js 中编码(encode)和解码(decode)的三种方法
    jQuery 页面加载初始化
    oracle 索引失效原因_汇总
    jdbc连接数据库使用sid和service_name的区别
    作为首席架构师,我是如何选择并落地架构方案的?
    (二)、JAVA运行时数据区域
    (一)、Java内存模型
    Java中Volatile关键字详解
  • 原文地址:https://www.cnblogs.com/stacklike/p/8098088.html
Copyright © 2020-2023  润新知