21. 面向对象之继承

1. 概念

继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类

python中类的继承分为:单继承和多继承

2. 单继承、多继承、查看继承

2.1 单继承

新类只继承一个基类

class Animal:
    category = '动物'

    def eat(self):
        print('动物可以吃东西')

    def speak(self):
        print('动物可以发出声音')


class Duck(Animal):  # 继承了Animal类中的所有方法和属性
    category = '鸭子'

    def swim(self):
        print('鸭子可以游泳')


obj = Duck()
obj.eat()  # 动物可以吃东西
print(obj.category)  # 鸭子   先在自己的属性字典中找

2.2 多继承、查看继承

新类继承多个基类

class Animal:
    category = '动物'

    def eat(self):
        print('动物可以吃东西')

    def speak(self):
        print('动物可以发出声音')


class Duck(Animal):  # 继承了Animal类中的所有方法和属性
    category = '鸭子'

    def swim(self):
        print('鸭子可以游泳')


class DonaldDuck():
    def play(self):
        print('Donald喜欢玩')


class SmallDuck(DonaldDuck, Duck):
    def eat(self):
        print('小鸭子喜欢吃东西')


obj = SmallDuck()
obj.eat()  # 小鸭子喜欢吃东西    先从自己的属性字典中找
print(obj.category)  # 鸭子   基类DonaldDuck中没有,去基类Duck中找
obj.play()  # Donald喜欢玩    去基类DonaldDuck中找

# _ _base_ _查看从左到右继承的第一个基类
# _ _bases_ _查看所有继承的基类
print(SmallDuck._ _bases_ _)  # (<class '__main__.DonaldDuck'>, <class '__main__.Duck'>)
print(SmallDuck._ _base_ _)  # <class '__main__.DonaldDuck'>

print(Animal._ _bases_ _)  # (<class 'object'>,)
print(Animal._ _base_ _)  # <class 'object'>

3. 经典类与新式类

只有在python2中才分新式类和经典类,python3中都是新式类

在python2中,没有明显地继承object类,以及该类的子类,都是经典类

在python2中,明显地声明继承object类,以及该类的子类,都是新式类

在python3中,都默认继承object,即python3中所有类均为新式类

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

4. 属性的查找顺序

4.1 不隐藏属性

有了继承关系,对象在查找属性时,先从对象自己的属性字典中找,没有则去基类中找

class Teacher:
    def f1(self):
        print('当前是Teacher的f1')

    def f2(self):  # (3)在基类中找到了f2方法
        print('当前是Teacher的f2')
        print(self)  # (8)查看self的本质  <__main__.Student object at 0x000001BD9E42B9D0>
        # (4)self的本质是一个对象,因此这里的self是stu1对象
        #    而stu1对象的属性字典中有f1方法,该方法在产生对象的Student类中
        #    因此又回到了Student类中
        self.f1()


class Student(Teacher):  # (2)来到Student类中找f2方法,发现没有,于是去基类Teacher中查找
    def f1(self):  # (5)找到了f1方法
        print('当前是Student的f1')


stu1 = Student()  # 实例化得到一个对象stu1
stu1.f2()  # (1)stu1对象调用f2方法
# (6)因此打印的顺序是'当前是Teacher的f2,'当前是Student的f1'
# 当前是Teacher的f2
# 当前是Student的f1



# (7)查看stu1的本质
print(stu1)  # <__main__.Student object at 0x000001BD9E42B9D0>
# (9)根据步骤7和步骤8可以得出结论,Teacher类f2函数的self和对象stu1的内存地址一致

4.2 隐藏属性

class Teacher:
    def __f1(self):  # 封装之后函数名变成_Teacher__f1
        print('当前是Teacher的f1')

    def f2(self):  # (2)发现Student类中没有,于是来到基类Teacher中找到f2
        print('当前是Teacher的f2')
        # print(self)  # (6)查看self的本质<__main__.Student object at 0x000002BA5A73B9D0>
        self.__f1()  # (3)本质是stu1._Teacher__f1()

        # (4)发现对象stu1的属性字典中并没有_Teacher__f1,于是只能在类Teacher中找_Teacher__f1
        # print(stu1.__dict__)  # {}
        # print(Teacher.__dict__)  # '_Teacher__f1': <function Teacher.__f1 at 0x00000216C9D13370>, 'f2': <function Teacher.f2 at 0x00000216C9FE1750>,


class Student(Teacher):
    def __f1(self):  # 封装之后函数名变成_Student__f1
        print('当前是Student的f1')


stu1 = Student()  # 实例化得到一个对象stu1
stu1.f2()  # (1)stu1对象调用f2方法
# (5)因此打印的顺序是'当前是Teacher的f1',当前是Teacher的f2
# 当前是Teacher的f1'
# 当前是Teacher的f2


# (7)查看stu1的本质
# print(stu1)  # <__main__.Student object at 0x000002BA5A73B9D0>
# (8)根据步骤6和步骤7可以得出结论,Teacher类f2函数的self和对象stu1的内存地址一致

4.3 总结

在没有隐藏属性的情况下,属性的查找顺序是:

对象属性字典---实例化得到该对象的类---基类---再上一级基类

 

在隐藏属性的情况下,属性的查找顺序是:

从当前执行该方法的类中去找封装属性---找不到就报错

5. 继承与抽象

抽象:抽取类似或者比较像的部分

哈兰德、基利安—— 人

杰瑞、泰菲—— 鼠

斯派克、泰克—— 狗

人、鼠、狗—— 动物

 

继承:基于抽象的结果,得到这些比较像的部分

动物—— 人、鼠、狗

人—— 哈兰德、基利安

鼠—— 杰瑞、泰菲

狗—— 斯派克、泰克

 6. 菱形继承的属性查找

6.1 概念

菱形继承属性查找方式有两种,分别是深度优先和广度优先,区分条件是新式类和经典类。

6.2 代码

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):
    pass
obj = F()
obj.test()  # from D

6.3 经典类:深度优先

 深度优先,先不分开去找,先一个方向找到最顶层基类,如果没找到则从另一个方向找

F--D--B--A   E--C

6.4 新式类:广度优先

广度优先,先不找最顶层的基类,一个方向找不到去另一个方向找,最后找顶层基类

F--D--B   E--C--A

6.5 查看类的查找顺序

_ _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):
    pass

print(F.__mro__)
# (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

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

7. 派生

 7.1 概念

派生是指子类在继承基类的属性后,衍生出自己的属性,并且重写基类的属性

7.2 引入

class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def lab(self):
        print('实验')

class Student(Person):
    def __init__(self, course):
        self.course = course
    def lab(self):
        print('学生正在做实验')

stu1 = Student(course='控制工程')
print(stu1.course)  # 控制工程
print(stu1.school)  # MIT
stu1.lab()  # 学生正在做实验

# 找不到grade,原因是在子类中又重写了基类_ _ init _ _的方法,导致了原本的init方法失效
print(stu1.grade)  # AttributeError: 'Student' object has no attribute 'grade'

7.3 子类派生属性中继承基类属性的两种方法

方法一:调用基类的函数

class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def lab(self):
        print('实验')

class Student(Person):
    def __init__(self, course, name, age, grade):
        Person.__init__(self, name, age, grade)  # 子类从基类中继承属性
        self.course = course

    def lab(self):
        print('学生正在做实验')

stu1 = Student(name='lavigne', age=20, grade=2, course='control')
print(stu1.course)  # control
print(stu1.school)  # MIT

方法二:使用内置函数super

调用super()会得到一个特殊的对象,该对象专门用来引用基类的属性,且严格按照MRO规定的顺序向后查找

class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def lab(self):
        print('实验')

class Society:
    def __init__(self, job):
        self.job = job

    def vocation(self, where):
        print(f'在{where}度假')

class Student(Society, Person):
    def __init__(self, course, job):
        super().__init__(job)  # super只能继承第一个基类,如果继承第二个基类的属性则会报错
        self.course = course

    def holiday(self, where):
        super().vocation(where)  # 继承基类的函数属性

stu1 = Student(course='控制工程', job='学生')
print(stu1.course)  # 控制工程

# 使用super加载基类中的属性
print(stu1.job)  # 学生
stu1.holiday(where='黄金海岸')  # 在黄金海岸度假

 

class Society:
    def __init__(self, job):
        self.job = job

    def vocation(self, where):
        print(f'在{where}度假')


class Student(Society):
    def __init__(self, course, work):
        super().__init__(work)  # super只能继承第一个基类,如果继承第二个基类的属性则会报错
        self.course = course


stu1 = Student(course='控制工程', work='学生')

# 使用super加载基类中的属性
print(stu1.job)  # 学生  类Student里面的work只是个中间参数,最终的值是类Society里面的job

 

方法三:方法一与方法二的组合

class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def lab(self):
        print('实验')


class Society:
    def __init__(self, job):
        self.job = job

    def vocation(self, where):
        print(f'在{where}度假')


class Student(Society, Person):
    def __init__(self, course, job, name, age, grade):
        Person.__init__(self, name, age, grade)
        super().__init__(job)  # super只能继承第一个基类,如果继承第二个基类的属性则会报错
        self.course = course

    def holiday(self, where):
        super().vocation(where)  # 继承基类的函数属性


stu1 = Student(course='Music', job='学生', name='lavigne', age=20, grade=2)
print(stu1.course)  # 控制工程

# 查看基类中的属性
print(stu1.job)  # 学生
print(stu1.name)  # lavigne
print(stu1.age)  # 20
stu1.holiday(where='黄金海岸')  # 在黄金海岸度假

总结:

方式一注意调用函数的时候要传入self,方式二super函数继承的是第一个基类

8. 组合

在一个类中,以另外一个类的对象作为数据属性,称为类的组合

 

class Course:
    def __init__(self, kind, teacher):
        self.kind = kind
        self.teacher = teacher

    def show_info(self):
        print(f'课程是{self.kind},教师是{self.teacher}')


class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def show_birthday(self):
        return f'生日是{self.year}年{self.month}月{self.day}'


class Person:
    school = 'MIT'

    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade


class Student:
    def __init__(self, name, age, grade, year, month, day):
        Person.__init__(self, name, age, grade)
        self.birthday = Date(year, month, day)  # 生日信息,构建了一个对象
        self.courses = []  # 课程信息

    def stu_info(self):
        print(f'学生是{self.name},年龄是{self.age},年级是{self.grade}'
              f'生日是{self.birthday.show_birthday()}')


stu1 = Student(name='ronaldo', age=20, grade=1, year=2000, month='Jan', day=1)
music = Course(kind='music', teacher='lavigne')
art = Course(kind='art', teacher='avril')
stu1.courses.append(music)  # 参数是一个对象
stu1.courses.append(art)  # 参数是一个对象
stu1.stu_info()  # 学生是ronaldo,年龄是20,年级是1生日是生日是2000年Jan月1
print(stu1.birthday.show_birthday())  # 生日是2000年Jan月1
stu1.courses[0].show_info()  # 课程是music,教师是lavigne

 

继承:子类继承了父类的数据和方法,衍生出自己的数据和方法

组合:将多个类组合到一起,组合后的类拥有多个类的数据和方法

9. 抽象类

9.1 概念

抽象类是一个特殊的类,只能被继承,不能被实例化

如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子,你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于,抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法

 9.2 不重写抽象类方法

from abc import ABC, abstractmethod
class Animal(ABC):
    def __init__(self, name):
        self.name = name
    @abstractmethod
    def run(self):
        pass

    @abstractmethod
    def speak(self):
        pass

    def eat(self):
        pass

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name)

dog1 = Dog('小黑')  # 报错,子类没有定义抽象方法
# TypeError: Can't instantiate abstract class Dog with abstract methods run, speak

9.3 重写抽象类方法

from abc import ABC, abstractmethod

class Animal(ABC):
    def __init__(self, name):
        self.name = name

    @abstractmethod  # 定义抽象方法,无需实现功能
    def run(self):
        pass

    @abstractmethod  # 定义抽象方法,无需实现功能
    def eat(self):
        pass

    def speak(self):
        pass

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name)
    def run(self):  # 子类继承抽象类,必须定义抽象方法
        print('跑起来了')

    def eat(self):
        print('进食')


dog1 = Dog('小黑')
dog1.run()  # 跑起来了
dog1.eat()  # 进食

 

posted @ 2024-08-16 01:00  hbutmeng  阅读(9)  评论(0编辑  收藏  举报