Python基础53:面向对象三大特性--继承

# **************************************************************************
# Python学习
# **************************************************************************
# ** 所属主题: 类
# ** 所属分层: 05 面向对象三大特性--继承
# ** 功能描述: 05 面向对象三大特性--继承
# ** 创 建 者: 陈红伟
# ** 创建日期: 2021/6/19 5:53 下午
# **************************************************************************
# ** 修改日期     修改人    修改内容
# ** 2021/6/19   陈红伟    新增学习内容代码
# **************************************************************************
"""
本章内容:
    继承:
        单继承,多继承,__bases__(查看类继承关系) , __dict__(查看对象中的属性),
        经典类,新式类,派生(重写),菱形继承,深度优先查找,广度优先查找,mro(),Mixins机制
"""

"""
一、什么是继承
1、继承是一种创建新类的方式,新建的子类可称之为子类或者派生类,父类又可以称之为基类或超类,子类会遗传父类的属性
2、需要注意的是python支持多继承
    在python中,新建的类可以继承一个或多个父类
"""


class Parent1:
    pass


class Parent2:
    pass


class Sub1(Parent1):  # 单继承
    pass


class Sub2(Parent1, Parent2):  # 多继承
    pass


print(Sub1.__bases__)  # (<class '__main__.Parent1'>,)
print(Sub2.__bases__)  # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

"""
Ps:在python2中有金典类和新式类之分:
新式类:继承了object类的子类,以及该类的子类子子类
经典类:没有继承object类的子类,以及该类的子类子子类

Ps:在python3中没有继承任何类,那么会默认继承object类,所以python3中所有的类都叫新式类。
    所以为了兼容Python2,我们可以在创建类的时候写上继承object(如果没有要求要继承其他类)
    例如:class Person(object)
"""


# 二、python的多继承
# 优点:子类可以同事遗传多个父类的属性,最大限度的重用代码
# 缺点:
#     1、违背人的思维习惯:继承表达的是一种什么是'什么'的关系
#     2、代码可读性变差
#     3、不建议使用多继承,可扩展性太差
#     ps:如果真的涉及到一个子类不可避免的要重用多个父类的的属性,应该使用Mixins机制


# 三、如何使用继承: 为了解决类与类之间的冗余问题
# 示范:
class ChwPeople(object):
    school_name = 'chw'

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


class Student(ChwPeople):
    # school_name = 'chw'

    # def __init__(self, name, age, sex):
    #     self.name = name
    #     self.age = age
    #     self.sex = sex

    def choose_course(self):
        print(f'{self.name}正在选课')


stu1 = Student('s1', 16, '男')  # 继承之后当前类找不到就会去父类找
print(stu1.__dict__)  # {'name': 's1', 'age': 16, 'sex': '男'}
print(stu1.school_name)  # chw


# 尖叫提示:如果父类中有定义了属性和方法,子类中也定义,就叫派生 例如Teacher类的school_name和__init__
class Teacher(ChwPeople):
    school_name = 'chw'  # 派生

    # 派生
    def __init__(self, name, age, sex, salary, level):
        # self.name = name  # 因为该类的基类中定义了这三个属性,所以不需要自己定义
        # self.age = age    # 因为该类的基类中定义了这三个属性,所以不需要自己定义
        # self.sex = sex    # 因为该类的基类中定义了这三个属性,所以不需要自己定义
        # 所以要指名道姓的跟基类要:
        ChwPeople.__init__(self, name, age, sex)
        self.salary = salary
        self.level = level

    def sorce(self):
        print(f'{self.name}老师在给学生打分')


t1 = Teacher('李老师', 48, '男', 15000, 'A')
# print(t1.__dict__)
#  {'salary': 15000, 'level': 'A'}  【没有加ChwPeople.__init__(self, name, age, sex)这行代码】注释掉后只拿到了两个属性,说明调用的是类Teacher中的init方法
print(t1.__dict__)
# 【加了ChwPeople.__init__(self, name, age, sex)这行代码】 {'name': '李老师', 'age': 48, 'sex': '男', 'salary': 15000, 'level': 'A'}


""" 
继承中的菱形继承(死亡钻石)
    1、什么是菱形继承:
        类:D多继承B和C , B和C都继承A  ,A又是一个非object的类。 这种就叫做菱形继承
    2、菱形结构带来的问题:
        属性查找顺序不一致:【深度优先查找】(一天路走到黑)和【广度优先查找】(最后在找根结点)
        【深度优先查找】(金典类用的):沿着一个分支一直找到根结点,在找下一个分支(后面就不会找根结点了,因为已经找过了),直至找到。
        【广度优先查找】(新式类用的):沿着一个分支一直找,直到不是根结点,在找下一个分支,直至找到,最后在找根节点。
        
    3、属性查找顺序可以用mro来看: 类名.mro()  
        ps:【python3才有,所以mro是广度查找,因为python都是新式类,都会继承object】
        类的mro(): 类以及类的对象访问属性都是参照该类的mro列表,也就是类中属性查找顺序,但是object一定是最后一个找的
    
总结:
    如果多继承是【非菱形继承】,python2和python3中类的属性查找顺序一样,都是一个一个分支找下去,最后在找object
    如果多继承是【菱形继承】,python2(中的经典类)和python3(新式类)中类的属性查找顺序不一样,前者是深度优先查找,后者是广度优先查找
"""


class A:
    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, C):
    pass


class E(C, B):
    pass


""" 
    类的mro(): 类以及类的对象访问属性都是参照该类的mro列表,也就是类中属性查找顺序,但是object一定是最后一个找的
"""
obj = D()
# 类D以及类D的对象访问属性都是参照该类的mro列表
print(
    D.mro())  # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, # <class 'object'>]
obj.test()  # from B

obj = E()
print(
    E.mro())  # [<class '__main__.E'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
obj.test()  # from C

""" 
继承总结:
一、多继承到底又不要用?
    要,但是要规避几点问题:
    1、继承结构尽量不要过于复杂
    2、推荐使用mixins机制:要在多继承的背景下满足继承的什么"是"什么的关系  ==> mixins    
"""

"""
多继承的正确打开方式:
mixins机制

    核心:就是在多继承背景下尽可能的提升多继承的可读性 
"""


# 交通工具类
class Vehicle(object):
    def fly(self):
        pass


# 民航飞机类
class MinHangFeiJi(Vehicle):
    pass


# 直升飞机类
class ZhiShengFeiJi(Vehicle):
    pass


# 汽车类
class Car(Vehicle):  # 汽车不会非飞,但是按照上述继承关系,他也会飞,所以得新建一个飞行工具的类
    pass


print("======这是一条华丽的分割线=======")


# 交通工具类
class Vehicle(object):
    pass


# 飞行工具
class FlyableMixin():  # 用Mixin后缀来标识他只是用来记录是属于部分分类的交通工具额外的功能,java中只有单继承,用接口来解决此问题
    def fly(self):
        pass


# 爬行工具
class PaxingMixin():  # 用Mixin后缀来标识他只是用来记录是属于部分分类的交通工具额外的功能,java中只有单继承,用接口来解决此问题
    def pa(self):
        pass


# 民航飞机类
class MinHangFeiJi(Vehicle, PaxingMixin):
    pass


# 直升飞机类
class ZhiShengFeiJi(Vehicle, PaxingMixin):
    pass


# 汽车类
class Car(Vehicle):
    pass


""" 
在子类派生的新方法中重用父类的功能:
    方式一:指名道姓调用某一个类下的函数  ==>  不依赖于继承关系
    方式二:super(),调用父类提供给自己的方法  ==>  严格的依赖继承关系
            调用super()会得到一个特殊的对象,该对象会参照当前类的mro,去当前类的父类中找属性
            尖叫提示⚠️:super是根据发起属性查找的那个类的mro。去当前类中找属性
"""


# 方式一、指名道姓调用某一个类下的函数
class C1:
    def __init__(self, name):
        self.name = name

    def f1(self):
        print('C1 f1')


class B1(C1):
    def __init__(self, name, b1, b2):
        C1.__init__(self, name)  # 方式一:指名道姓调用某一个类下的函数
        self.b1 = b1
        self.b2 = b2


b1 = B1('b1', 1, 1)
print(b1.__dict__)  # {'name': 'b1', 'b1': 1, 'b2': 1}


# 方式二、super(),调用父类提供给自己的方法
class C1:
    def __init__(self, name):
        self.name = name

    def f1(self):
        print('C1 f1')


class B1(C1):
    def __init__(self, name, b1, b2):
        # C1.__init__(self, name)  # 方式一:指名道姓调用某一个类下的函数
        # super().__init__(name)  # python2写法:调用的是方法,自动传入对象
        super(B1, self).__init__(name)  # python3写法:调用的是方法
        self.b1 = b1
        self.b2 = b2


b1 = B1('b1', 1, 1)
print(b1.__dict__)  # {'name': 'b1', 'b1': 1, 'b2': 1}


# 特别提醒⚠️:
# 尖叫提示⚠️:super是根据发起属性查找的那个类的mro。去当前类中找属性
class A:
    def test(self):
        print('A test')


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


class C(A,B):
    pass


c = C()
c.test()  # A test # 广度查找


class A:
    def test(self):
        print('A test')
        super(A, self).test1()


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

    # def test1(self):
    #     print('B test1')


class C(A,B):
    def test1(self):
        print("C test1")


c = C()
print(C.mro()) # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
# c.test()  # AttributeError: 'super' object has no attribute 'test1'

上一篇:53. Django 2.1.7 redirect重定向数据传输的问题


下一篇:7-53 两个有序序列的中位数 (25 分)