• 第38讲:类和对象——继承


    一  继承

    1 继承的基本概念

    • 定义:继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
    • 分类:python中类的继承分为:单继承和多继承
    • 语法:
      1 class 类名(父类1, 父类2, ...):
      2     #类定义部分
    • 特点:
      • 如果该类没有显式指定继承自哪个类,则默认继承 object 类(object 类是 Python 中所有类的父类/基类,即要么是直接父类,要么是间接父类)。
      • Python 的继承是多继承机制(和 C++ 一样),即一个子类可以同时拥有多个直接父类。
    • 查看:
      • 类名.__base__:只查看从左到右继承的第一个子类
      • 类名.__bases__:查看所有继承的父类
    • 经典类与新式类:
      • 只有在python2中才分新式类和经典类,python3中统一都是新式类
      • 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
      • 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
      • 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类

    2 继承与抽象(先抽象再继承)

    • 抽象:
      • 即抽取类似或者说比较像的部分。
      • 抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
      • 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
    • 继承:
      • 是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
      • 继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承

    3 继承与重用性

    • 代码重用:通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
    • 开发过程中,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.

    4 派生

    • 定义:它和继承是一个意思,只是观察角度不同而已。换句话说,继承是相对子类来说的,即子类继承自父类;而派生是相对于父类来说的,即父类派生出子类。
    • python父类方法的重写:
      • 适用情况:对于父类中的一些方法,子类对象不能直接使用时,必须将这些方法在子类中重新定义一遍,即重写
      • 定义:重写,有时又称覆盖,是一个意思,指的是对类中已有方法的内部实现进行修改。
      • 举例:
         1 class Bird:
         2     #鸟有翅膀
         3     def isWing(self):
         4         print("鸟有翅膀")
         5     #鸟会飞
         6     def fly(self):
         7         print("鸟会飞")
         8 class Ostrich(Bird):
         9     # 重写Bird类的fly()方法
        10     def fly(self):
        11          print("鸵鸟不会飞")
        12 
        13 # 创建Ostrich对象
        14 ostrich = Ostrich()
        15 #调用 Ostrich 类中重写的 fly() 类方法
        16 ostrich.fly()
        方法重写
      • 调用重写之后的方法:子类对象名.重写后的方法,例如"ostrich.fly()"语句
      • 调用被重写方法:父类名.被重写的方法名(子类对象名)
        • 1  # 创建Ostrich对象
          2  ostrich = Ostrich()
          3  #调用 Bird 类中的 fly() 方法
          4  Bird.fly(ostrich)
          View Code
        • Python 中的类可以看做是一个独立空间,而类方法其实就是出于该空间中的一个函数。
        • 而如果想要全局空间中,调用类空间中的函数,只需要在调用该函数时备注类名即可。
        • 使用类名调用其类方法,Python 不会为该方法的第一个 self 参数自定绑定值,因此采用这种调用方法,需要手动为 self 参数赋值。

    5 关于python的多继承

    • 大部分面向对象的编程语言,都只支持单继承,即子类有且只能有一个父类。而Python 却支持多继承(C++也支持多继承)
    • 和单继承相比,多继承容易让代码逻辑复杂、思路混乱,一般较少使用
    • 使用多继承经常需要面临的问题是,多个父类中包含同名的类方法
      • 对于这种情况,Python 的处置措施是:根据子类继承多个父类时这些父类的前后次序决定,即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。
      • 代码:
      • 多个父类中包含同名类方法解决办法
      • 运行结果:
      • 1 People类 张三

    6 继承的实现原理

    • 继承顺序问题(继承了多个类):
      • Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先广度优先
    • 经典类和新式类的区别:
      • 语法区分:是否继承了object类
      • 多继承查找方式区分:
        • 当类是经典类时,多继承情况下,会按照深度优先方式查找
          • 1 class D:
             2 
             3     def bar(self):
             4         print 'D.bar'
             5 
             6 
             7 class C(D):
             8 
             9     def bar(self):
            10         print 'C.bar'
            11 
            12 
            13 class B(D):
            14 
            15     def bar(self):
            16         print 'B.bar'
            17 
            18 
            19 class A(B, C):
            20 
            21     def bar(self):
            22         print 'A.bar'
            23 
            24 a = A()
            25 # 执行bar方法时
            26 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
            27 # 所以,查找顺序:A --> B --> D --> C
            28 # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
            29 a.bar()
            30 
            31 经典类多继承
            
            经典类多继承
            经典类多继承
          • 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
        • 当类是新式类时,多继承情况下,会按照广度优先方式查找
          • 1 class D(object):
             2 
             3     def bar(self):
             4         print 'D.bar'
             5 
             6 
             7 class C(D):
             8 
             9     def bar(self):
            10         print 'C.bar'
            11 
            12 
            13 class B(D):
            14 
            15     def bar(self):
            16         print 'B.bar'
            17 
            18 
            19 class A(B, C):
            20 
            21     def bar(self):
            22         print 'A.bar'
            23 
            24 a = A()
            25 # 执行bar方法时
            26 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
            27 # 所以,查找顺序:A --> B --> C --> D
            28 # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
            29 a.bar()
            30 
            31 新式类多继承
            
            新式类多继承
            新式类多继承

            首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错

    • 继承原理(python如何实现继承)
      • 原理:
        • python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表——F.mro() #等同于F.__mro__
        • 为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
      • MRO列表
        • 构造:通过一个C3线性化算法来实现
        • 实质:它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
          • 子类会先于父类被检查
          • 多个父类会根据它们在列表中的顺序被检查
          • 如果对下一个类存在两个合法的选择,选择第一个父类

    7 子类中调用父类的方法

    • 问题:
      • 当一个子类继承多个类时,在创建子类的对象后,程序运行可能会出错;
      • 因为不同父类的构造方法之间会被覆盖,导致有些父类的构造方法没有得到正确的参数;
      • 为解决该问题,正确的做法是在子类中定义自己的构造方法(等同于重写第一个直接父类的构造方法),并且必须在该方法中调用第一个直接父类的构造方法
    • 调用第一个父类的构造方法:
      • 方法一:父类名.父类构造方法()
      • 方法二:super()
        • Python 2.x 语法:
          1 super(Class, obj).__init__(self,...)
        • python 3.x 语法:
          1 super().__init__(self,...)
    • 上述两种方式也可用于调用父类中的其它方法:
      • 方法一:父类名.父类方法()
        • 1 #_*_coding:utf-8_*_
           2 __author__ = 'Linhaifeng'
           3 
           4 class Vehicle: #定义交通工具类
           5      Country='China'
           6      def __init__(self,name,speed,load,power):
           7          self.name=name
           8          self.speed=speed
           9          self.load=load
          10          self.power=power
          11 
          12      def run(self):
          13          print('开动啦...')
          14 
          15 class Subway(Vehicle): #地铁
          16     def __init__(self,name,speed,load,power,line):
          17         Vehicle.__init__(self,name,speed,load,power)
          18         self.line=line
          19 
          20     def run(self):
          21         print('地铁%s号线欢迎您' %self.line)
          22         Vehicle.run(self)
          23 
          24 line13=Subway('中国地铁','180m/s','1000人/箱','',13)
          25 line13.run()
          非绑定方法
      • 方法二:super()
        • 1 class Vehicle: #定义交通工具类
           2      Country='China'
           3      def __init__(self,name,speed,load,power):
           4          self.name=name
           5          self.speed=speed
           6          self.load=load
           7          self.power=power
           8 
           9      def run(self):
          10          print('开动啦...')
          11 
          12 class Subway(Vehicle): #地铁
          13     def __init__(self,name,speed,load,power,line):
          14         #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
          15         super().__init__(name,speed,load,power)
          16         self.line=line
          17 
          18     def run(self):
          19         print('地铁%s号线欢迎您' %self.line)
          20         super(Subway,self).run()
          21 
          22 class Mobike(Vehicle):#摩拜单车
          23     pass
          24 
          25 line13=Subway('中国地铁','180m/s','1000人/箱','',13)
          26 line13.run()
          super()函数

    有关多重继承的问题:https://fishc.com.cn/thread-48759-1-1.html

    二 课后作业

    测试题部分:

     0. 继承机制给程序猿带来最明显的好处是?
    答:可以偷懒,据说这是每一个优秀程序猿的梦想!
    如果一个类 A 继承自另一个类 B,就把这个 A 称为 B 的子类,把 B 称为 A 的父类、基类或超类。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码(偷懒)。
    在子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。另外,为子类追加新的属性和方法也是常见的做法。

    1. 如果按以下方式重写魔法方法 __init__,结果会怎样?

    1 class MyClass:
    2     def __init__(self):
    3         return "I love FishC.com!" 

    答:会报错,因为 __init__ 特殊方法不应当返回除了 None 以外的任何对象

    1 >>> myClass = MyClass()
    2 Traceback (most recent call last):
    3   File "<pyshell#13>", line 1, in <module>
    4     myClass = MyClass()
    5 TypeError: __init__() should return None, not 'str'

    2. 当子类定义了与相同名字的属性或方法时,Python 是否会自动删除父类的相关属性或方法?
    答:不会删除!Python 的做法跟其他大部分面向对象编程语言一样,都是将父类属性或方法覆盖,子类对象调用的时候会调用到覆盖后的新属性或方法,但父类的仍然还在,只是子类对象“看不到”。

    3. 假设已经有鸟类的定义,现在我要定义企鹅类继承于鸟类,但我们都知道企鹅是不会飞的,我们应该如何屏蔽父类(鸟类)中飞的方法?
    答:覆盖父类方法,例如将函数体内容写 pass,这样调用 fly 方法就没有任何反应了。

     1 class Bird:
     2     def fly(self):
     3         print("Fly away!")
     4 class Penguin(Bird):
     5     def fly(self):
     6         pass
     7 >>> bird = Bird()
     8 >>> penguin = Penguin()
     9 >>> bird.fly()
    10 Fly away!
    11 >>> penguin.fly()

    4. super 函数有什么“超级”的地方?
    答:super 函数超级之处在于你不需要明确给出任何基类的名字,它会自动帮您找出所有基类以及对应的方法。由于你不用给出基类的名字,这就意味着你如果需要改变了类继承关系,你只要改变 class 语句里的父类即可,而不必在大量代码中去修改所有被继承的方法。

    5. 多重继承使用不当会导致重复调用(也叫钻石继承、菱形继承)的问题,请分析以下代码在实际编程中有可能导致什么问题?

     1 class A():
     2     def __init__(self):
     3         print("进入A…")
     4         print("离开A…")
     5 class B(A):
     6     def __init__(self):
     7         print("进入B…")
     8         A.__init__(self)
     9         print("离开B…")
    10         
    11 class C(A):
    12     def __init__(self):
    13         print("进入C…")
    14         A.__init__(self)
    15         print("离开C…")
    16 class D(B, C):
    17     def __init__(self):
    18         print("进入D…")
    19         B.__init__(self)
    20         C.__init__(self)
    21         print("离开D…")

    答:多重继承容易导致重复调用问题,下边实例化 D 类后我们发现 A 被前后进入了两次。
    这有什么危害?我举个例子,假设 A 的初始化方法里有一个计数器,那这样 D 一实例化,A 的计数器就跑两次(如果遭遇多个钻石结构重叠还要更多),很明显是不符合程序设计的初衷的(程序应该可控,而不能受到继承关系影响)。

     1 >>> d = D()
     2 进入D…
     3 进入B…
     4 进入A…
     5 离开A…
     6 离开B…
     7 进入C…
     8 进入A…
     9 离开A…
    10 离开C…
    11 离开D…

    为了让大家都明白,这里只是举例最简单的钻石继承问题,在实际编程中,如果不注意多重继承的使用,会导致比这个复杂N倍的现象,调试起来不是一般的痛苦……所以一定要尽量避免使用多重继承。
    想更多的了解,阅读 -> 多重继承的陷阱:钻石继承(菱形继承)问题uf^F&2aJ

    6. 如何解决上一题中出现的问题?
    答:super 函数再次大显神威。

     1 class A():
     2     def __init__(self):
     3         print("进入A…")
     4         print("离开A…")
     5 class B(A):
     6     def __init__(self):
     7         print("进入B…")
     8         super().__init__()
     9         print("离开B…")
    10         
    11 class C(A):
    12     def __init__(self):
    13         print("进入C…")
    14         super().__init__()
    15         print("离开C…")
    16 class D(B, C):
    17     def __init__(self):
    18         print("进入D…")
    19         super().__init__()
    20         print("离开D…")
    21 >>> d = D()
    22 进入D…
    23 进入B…
    24 进入C…
    25 进入A…
    26 离开A…
    27 离开C…
    28 离开B…
    29 离开D…

    动动手部分:

    0. 定义一个点(Point)类和直线(Line)类,使用 getLen 方法可以获得直线的长度。

    提示:
    设点 A(X1,Y1)、点 B(X2,Y2),则两点构成的直线长度 |AB| = √((x1-x2)2+(y1-y2)2)
    Python 中计算开根号可使用 math 模块中的 sqrt 函数
    直线需有两点构成,因此初始化时需有两个点(Point)对象作为参数

    代码:

     1 import math
     2 
     3 class Point(object):
     4     def __init__(self,x=0,y=0):
     5         self.x = x
     6         self.y = y 
     7     
     8     def getX(self):
     9         return self.x 
    10     
    11     def getY(self):
    12         return self.y 
    13 
    14 class Line(object):
    15     def __init__(self,p1,p2):
    16         self.x = p1.getX() - p2.getX()
    17         self.y = p1.getY() - p2.getY()
    18         self.len = math.sqrt(self.x * self.x + self.y * self.y)
    19     
    20     def getLen(self):
    21         return self.len 
    22 
    23 p1 = Point(1,1)
    24 p2 = Point(4,5)
    25 line = Line(p1,p2)
    26 print(line.getLen())
    ex38.py
  • 相关阅读:
    用FileSystemWatcher监视文件系统
    生成随机汉字验证码
    MySQL学习笔记二
    python高级学习笔记
    boost bind 表达式中的是值语义还是指针语义?
    容器与适配器的个人总结
    subversion linux使用方法
    boost asio(初学示例)
    MySQL学习笔记一
    subversion 命令
  • 原文地址:https://www.cnblogs.com/luoxun/p/13509335.html
Copyright © 2020-2023  润新知