继承

发布时间 2024-01-05 19:14:29作者: 苏苏!!

继承

(一)什么是继承

  • 继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
  • 子类会“”遗传”父类的属性,从而解决代码重用问题(去掉冗余的代码)
  • python中类的继承分为:单继承和多继承

(二)单继承和多继承

"""单继承和多继承"""
# 定义一个父类
class A():
    pass
# 再定义一个父类
class B():
    pass

# 单继承,基类是A
class C(A):
    pass

# 多继承,python支持多继承,用逗号分隔可以继承多个类
class D(A,B):
    pass

(三)查看继承

  • __base__查看从左到右继承的第一个父类
  • __bases__查看继承的所有的父类
"""查看继承"""
# 定义一个父类
class A():
    pass
# 再定义一个父类
class B():
    pass

# 单继承,基类是A
class C(A):
    pass

# 多继承,python支持多继承,用逗号分隔可以继承多个类
class D(A,B):
    pass
# __base__只查看从左到右继承的第一个子类
print(C.__base__)# <class '__main__.A'>
# __base__只查看从左到右继承的第一个子类
print(D.__base__)# <class '__main__.A'>
# __bases__查看所有继承的父类
print(D.__bases__)# (<class '__main__.A'>, <class '__main__.B'>)

(四)经典类和新式类

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

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

"""经典类与新式类"""
# Python2区分经典类和新式类
# 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
# 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类

# Python3都是新式类
# 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类

class Student():
    def read(self):
        ...

class Student(object):
    def read(self):
        ...

(五)抽象和继承

  • 继承描述的是子类与父类之间的关系,是一种什么是什么的关系。
  • 要找出这种关系,必须先抽象再继承
  • 抽象即抽取类似或者说比较像的部分

(1)抽象

  • 抽象就是抽取类似或者比较像的部分
  • 抽象分成两个层次
    • 将奥巴马和梅西这俩对象比较像的部分抽取成类;
    • 将人,猪,狗这三个类比较像的部分抽取成父类。
  • 抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

img

(2)继承

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

img

(六)继承与重用性

(1)单独成类

  • 猫可以:喵喵叫、吃、喝、拉、撒
  • 狗可以:汪汪叫、吃、喝、拉、撒
  • 如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能
#猫和狗有大量相同的内容
class 猫:

    def 喵喵叫(self):
        print '喵喵叫'

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

class 狗:

    def 汪汪叫(self):
        print '喵喵叫'

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

(2)继承总类

  • 上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。
  • 如果使用 继承 的思想,如下实现:
    • 动物:吃、喝、拉、撒
    • 猫:喵喵叫(猫继承动物的功能)
    • 狗:汪汪叫(狗继承动物的功能)
class 动物:

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 猫(动物):

    def 喵喵叫(self):
        print '喵喵叫'
        
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 狗(动物):

    def 汪汪叫(self):
        print '喵喵叫'

(3)代码实现

  • 继承的代码实现
class Animal():
    def eat(self):
        print(f"{self.name}吃饭!")
    def drink(self):
        print(f"{self.name}喝水!")
    def shit(self):
        print(f"{self.name}拉屎!")
    def pee(self):
        print(f"{self.name}撒尿!")
class Cat(Animal):
    def __init__(self,name):
        self.name=name
    def cry(self):
        print(f"{self.name}喵喵叫")
class Dog(Animal):
    def __init__(self,name):
        self.name=name
    def cry(self):
        print(f"{self.name}汪汪叫")
# 实例化类得到对象
cat=Cat(name='小花')
cat.cry()# 小花喵喵叫
cat.eat()# 小花吃饭!

dog=Dog(name='大黄')
dog.cry()# 大黄汪汪叫
dog.eat()# 大黄吃饭!

(4)代码重用

  • 在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
  • 我们不可能从头开始写一个类B,这就用到了类的继承的概念。
  • 通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
  • 继承的代码实现
class Animal():
    def __init__(self,name):
        self.name=name
    def eat(self):
        print(f"{self.name}吃饭!")
    def drink(self):
        print(f"{self.name}喝水!")
    def shit(self):
        print(f"{self.name}拉屎!")
    def pee(self):
        print(f"{self.name}撒尿!")
class Cat(Animal):
    def cry(self):
        print(f"{self.name}喵喵叫")
class Dog(Animal):
    def cry(self):
        print(f"{self.name}汪汪叫")
cat=Cat(name='小花')
cat.cry()
cat.eat()
# 小花喵喵叫
# 小花吃饭!
dog=Dog(name='大黄')
dog.cry()
dog.eat()
# 大黄汪汪叫
# 大黄吃饭!

(5)小结

  • 提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大。
  • 注意:像cat.eat之类的属性引用,会先从实例中找eat然后去类中找,然后再去父类中找...直到最顶级的父类。

(七)属性查找顺序

(1)不隐藏属性

  • 有了继承关系,对象在查找属性时
    • 先从对象自己的__dict__中找
    • 如果没有则去子类中找
    • 然后再去父类中找……
class Foo:
    def f1(self):
        print('Foo.f1')

    # 【3】发现父类中找到了 f2 方法
    def f2(self):
        print('Foo.f2')
        # 【4】
        # 但是这里犯了难,这个 self 到底是 Foo 还是 Bar ?
        # 我们要时刻记得,源头是谁,这个self就是谁
        # 我们是从 对象 b 来的,而对象 b 又是 Bar 的对象
        # 我们从 Bar 找到了 Foo 类里面,所以我们的源头就是 Bar
        # 那这个 self 就是 Bar , 而 Bar 类里面有 f1 方法
        # 所以我们就会回到 Bar 类里面
        self.f1()


class Bar(Foo):
    # 【2】来到 Bar 类里面寻找 f2 方法,但是发现自己没有
    # 于是向上查找,去父类 Foo 中查找
    def f1(self):
        # 【5】 找到了 Bar 中的方法
        print('Bar.f1')


# 实例化类得到对象
b = Bar()
# 【1】对象调用方法
b.f2()
# 【6】所以打印的顺序是 Foo 类中的 f2 , Bar 类中的 f1
# Foo.f2
# Bar.f1
  • b.f2()会在父类Foo中找到f2
    • 先打印Foo.f2
    • 然后执行到self.f1(),即b.f1()
  • 仍会按照:对象本身->类Bar->父类Foo的顺序依次找下去
    • 在类Bar中找到f1
    • 因而打印结果为Foo.f1

(2)隐藏属性

  • 父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的
class Foo:
    # 变形为_Foo__fa
    def __f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        # 变形为self._Foo__fa,因而只会调用自己所在的类中的方法
        self.__f1()


class Bar(Foo):
    # 变形为_Bar__f1
    def __f1(self):
        print('Bar.f1')


b = Bar()
# 在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
b.f2()
# Foo.f2
# Foo.f1

# 隐藏属性 : 隐藏给当前类,除了当前类,其他继承的子类均不能找到我隐藏的属性

(八)继承实现的原理

(1)非菱形结构继承顺序

  • 在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
  • 如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

(2)菱形结构继承顺序

  • 如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

(1)深度优先

  • 当类是经典类时,多继承情况下,在查找属性不存在时,会按照深度优先的方式查找下去

img

(2)广度优先

  • 当类是新式类时,多继承情况下,在查找属性不存在时,会按照广度优先的方式查找下去
先找非公共的类,找完之后再去找公共的类

img

(3)广度优先代码

class A():
    def test(self):
        print("from A")
class B(A):
    def test(self):
        print("from B")
class C(B):
    def test(self):
        print("from C")
class D(C):
    def test(self):
        print("from D")
class E(C):
    def test(self):
        print("from E")
class F(D,E):
    def test(self):
        print("from F")
f=F()
f.test()# from F
# # 只有新式才有这个属性可以查看线性列表,经典类没有这个属性
print(F.mro())
# [<class '__main__.F'>,
# <class '__main__.D'>,
# <class '__main__.E'>,
# <class '__main__.C'>,
# <class '__main__.B'>,
# <class '__main__.A'>,
# <class 'object'>]


# 新式类:F D E C B A
# 经典类:F D C B A 
# python3中都是新式类
# python2中才分新式类与经典类

(3)继承原理(Python如何实现的继承)

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

    先找非公共的类,找完之后再去找公共的类
class A(object):
    def test(self):
        print('from A')


class B(A):
    def test(self):
        print('from B')


class C(A):
    def test(self):
        print('from C')


class D(B):
    def test(self):
        print('from D')


class E(C):
    def test(self):
        print('from E')


class F(D, E):
    # def test(self):
    #     print('from F')
    pass


f1 = F()
f1.test()

print(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列表并遵循如下三条准则:
    • 子类会先于父类被检查
    • 多个父类会根据它们在列表中的顺序被检查
    • 如果对下一个类存在两个合法的选择,选择第一个父类