Python面向对象 A-03 类的继承

类的继承

一、继承的介绍

1.1、继承的应用场景与作用

1.1.1、应用场景

之前的例子中,我们定义人这个类。实际上,我们都有职业角色,比如程序员、老师、医生、警察等。现在我们来定义一个程序员的类。这时候会发现,我们很多属性,其实在人这个类中已经定义了。比如姓名,年龄。如果在程序员这个类中重新定义一遍,很麻烦。故为解决代码重用问题,我们在程序开发的过程中引入继承。

1.1.2、作用

继承的功能之一就是用来解决代码重用问题

这样用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大节省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.

1.2、继承的相关定义

1.2.1、继承的定义

在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时我们不可能从头开始写一个类B,想让B‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。这个概念就叫做继承。

1.2.2、继承的派生类(子类)

上述所说,B遗传A的所属属性,其中B类就是派生类(子类)

1.2.3、继承的基类(父类)

上述所说,B遗传A的所属属性,其中A类就是基类(父类)

1.3、继承的分类

1.3.1、根据继承基类个数分类的单继承与多继承

在python中,新建的类可以继承一个或多个父类,父类又可以成为基类或超类,新建的类称为派生类或子类。可以分为单继承和多继承

a、单继承
语法
class DerivedClassName(BaseClassName1):
    <statement-1>
    .
    <statement-N>
b、多继承
语法
class DerivedClassName(BaseClassName1, BaseClassName2, BaseClassName3):   # python支持多继承,用逗号分隔开多个继承的类
    <statement-1>
    .
    <statement-N>
说明

需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

1.3.2、根据继承基类有无的经典类与新式类
  • 只有在python2中才分新式类和经典类,python3中统一都是新式类
  • 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
  • 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
  • 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类

1.4、继承的关系确认

1.4.1、如何通过抽象方法来确定类之间的继承关系

抽象最主要的作用 是划分类别(可以隔离关注点,降低复杂度)
抽象即抽取类似或者说比较像的部分。抽象分成两个层次:

  • 1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
  • 2.将人,猪,狗这三个类比较像的部分抽取成父类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eX97wtyI-1570688077003)(en-resource://database/2524:1)]

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
在这里插入图片描述

1.4.2、如何查看继承关系

通过查看类属性__bases__可以确认

代码示例
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
说明

注意:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。

>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)

二、继承的使用

2.1、继承后使用父类的方法与属性

2.1.1、使用继承过来的属性(类的实例变量、类变量。类方法)

由于父类继承了子类,所以对父类来说,也有了对应的 类的实例变量、类变量、类方法。

代码示例
class Person:
    # 类变量
    species = '人类'       # 物种:人类

    def __init__(self, name, age):
        # 类的实例变量
        self.name = name
        self.age = age

    # 类方法
    def speak(self):
        print("%s 说: 我 %d 岁。" % (self.name, self.age))


class Programmer(Person):
    pass

    def programming(self):
        print("我 %s ,会编程" % self.name)


if __name__ == '__main__':
    p1 = Person('张三', 18)
    p1.speak()
    p2 = Programmer('李四', 19)
    print(p2.name, p2.species)
    p2.speak()

输出结果

张三 说:18 岁。
李四 人类
李四 说:19 岁。
说明
  • 变量使用是通过类实例对象+.方法,比如 p2.namep2.species
  • 类方法的使用是通过类实例对象+.方法(),像运行函数一样()运行,比如p2.speak()
2.1.2、继承后修改与额外添加属性(类的实例变量、类变量。类方法)

当然继承过来的属性不满足我们的需要,我们需要给新的类添加额外的属性
存在如下的情况:
一、继承过来的某些属性,不满足我们需求,需要修改

  • 修改继承过来的变量
  • 修改继承过来的类的实例变量
  • 修改继承过来的类方法

二、继承过来的某些属性,不满足我们需求,需要额外添加

  • 添加额外的类变量
  • 添加额外类的实例变量(指明道姓 和 super方法)
  • 添加新的类方法
代码示例1
class Person:
    # 类的变量
    species = 'human'       # 物种:人类

    def __init__(self, name, age):
        # 类的实例变量
        self.name = name
        self.age = age

    # 类方法
    def speak(self):
        print("%s 说: 我 %d 岁。" % (self.name, self.age))


class Programmer(Person):
    # 类的变量(额外添加)
    occupation = '程序员'
    # 类的变量(修改)
    species = '人类'

    # 重写类的构造方法
    def __init__(self, lastname, firstname, work_type, age):
        # 类的实例变量
        self.firstname = firstname
        self.lastname = lastname
        self.work_type = work_type
        self.age = age
        self.working_age = 0

    # 重写类的方法
    def speak(self):
        print('我 %s%s ,会说话' % (self.lastname, self.firstname))

    # 定义自己的类方法
    def programming(self):
        print("我 %s%s ,会编程" % (self.lastname, self.firstname))


if __name__ == '__main__':
    p1 = Person('张三', 18)
    print(p1.name, p1.species)
    p1.speak()
    p2 = Programmer('李', '四', 'Python开发工程师', 19)
    print(p2.lastname, p2.firstname, p2.species, p2.occupation)
    p2.speak()
    p2.programming()

输出结果

张三 human
张三 说:18 岁。
李 四 人类 程序员
我 李四 ,会说话
我 李四 ,会编程
说明1
  • 修改了继承过来的类实例变量,我们发现类person中定义的类实例变量不好用,于是我们将name重新定义成firstname,lastname(名,姓)于是重写了类的构造方法 def __init__(self, lastname, firstname, work_type, age): 并添加了额外的类实例变量 work_type。
  • 修改了继承过来的类变量 species = 'human',将它的值重human 改成了 人类。
  • 修改了继承过来的类方法 def speak(self): 使它适用新的类实例变量 lastname 和 firstname。
  • 额外添加了类变量 occupation = '程序员'
  • 额外添加了类方法 programming(self)

但有时候,我们不需要全部修改,希望在父类的基础上使用其属下

代码示例2
class Person:
    # 类的变量
    species = 'human'       # 物种:人类

    def __init__(self, lastname, firstname, age):
        # 类的实例变量
        self.firstname = firstname
        self.lastname = lastname
        self.age = age

    # 类方法
    def speak(self):
        print('我 %s%s ,会说话' % (self.lastname, self.firstname))


class Programmer(Person):
    # 类的变量(额外添加)
    occupation = '程序员'
    # 类的变量(修改)
    species = '人类'

    # 重写类的构造方法
    def __init__(self, lastname, firstname, work_type, age):
        # 类的实例变量
        Person.__init__(self, lastname, firstname, age)
        self.work_type = work_type
        self.working_age = 0

    # 重写类的方法
    def speak(self):
        Person.speak(self)
        print('还会写程序说hello')

    # 定义自己的类方法
    def programming(self):
        print("我 %s%s ,会编程" % (self.lastname, self.firstname))


if __name__ == '__main__':
    p1 = Person('张', '三', 18)
    print(p1.lastname, p1.firstname, p1.species)
    p1.speak()
    p2 = Programmer('李', '四', 'Python开发工程师', 19)
    print(p2.lastname, p2.firstname, p2.species, p2.occupation)
    p2.speak()
    p2.programming()

输出结果

张 三 human
我 张三 ,会说话
李 四 人类 程序员
我 李四 ,会说话
还会写程序说hello
我 李四 ,会编程
说明2

通过指名道姓,即父类名.父类方法(), Person.__init__(self, lastname, firstname, age)Person.speak(self),我们可以在父类的基础上额外添加属性,而不需要想上面代码一样,大量的修改。注意,指名道姓的方法,需要传递的第一个参数是self

代码示例3
class Programmer(Person):
    # 类的变量(额外添加)
    occupation = '程序员'
    # 类的变量(修改)
    species = '人类'

    # 重写类的构造方法
    def __init__(self, lastname, firstname, work_type, age):
        # 类的实例变量
        # Person.__init__(self, lastname, firstname, age)
        super().__init__(lastname, firstname, age)
        self.work_type = work_type
        self.working_age = 0

    # 重写类的方法
    def speak(self):
        #Person.speak(self)
        super().speak()
        print('还会写程序说hello')

    # 定义自己的类方法
    def programming(self):
        print("我 %s%s ,会编程" % (self.lastname, self.firstname))


if __name__ == '__main__':
    p1 = Person('张', '三', 18)
    print(p1.lastname, p1.firstname, p1.species)
    p1.speak()
    p2 = Programmer('李', '四', 'Python开发工程师', 19)
    print(p2.lastname, p2.firstname, p2.species, p2.occupation)
    p2.speak()
    p2.programming()
说明3

使用super()方法,达到相同效果—推荐使用, super().__init__(lastname, firstname, age)super().speak()

2.3、继承后属性的查找顺序与规则

2.3.1、背景介绍

python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如

>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, 
<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

为了实现继承,python会在MRO列表上从左到右开始查找基类, 直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

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

在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,在Python3中,会按照深度优先的原则访问,但object基类特殊。(注:Python3中所有的类,默认是继承object这个类的)

2.3.2、举例说明
a、关系图

我们假定类的关系如下:
在这里插入图片描述

b、相关代码
class A(object):
    pass

class B(object):
    pass

class C(A):
    pass

class D(object):
    pass

class E(B,C):
    pass

class F(D):
    pass

class G:
    pass

class H(E,F,G):
    pass

print(H.mro())

运行结果

[<class '__main__.H'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
c、说明

上述示例的结果,访问顺序是 H-E-B-C-A-F-D-G-object ,这样证实了是安装深度优先的规则访问的。如果改名多继的类顺序,访问的顺序也会发生变化。可以下去自己尝试一下。
这样你就知道继承下的实例,访问类中成员属性的顺序了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值