python之继承
- 2021 年 4 月 15 日
- 筆記
一、什么是继承
继承是一种新建子类的方式,新建的称为子类/派生类,被继承的类称为父类
子类会遗传父类的属性,即可以访问和调用父类的属性
二、为什么要有继承
为了解决定义多个类时,代码冗余的问题。当我们在定义多个存在相同属性与功能的类时,相同代码可能会复写多次,我们可以将这些相同的代码抽出来,放到一个公共的类当中,也就是父类当中,其余类继承父类,这样相同代码只需写一遍,并且其余的类可以用到。
- 在python中可以继承一个类,也可以继承多个类
- 在python3中如果一个类没有继承任何父类,那么默认继承object类
- 但凡是继承了object类的子类,以及该子类的子子孙孙类都能用到object内的功能,称之为新式类
- 没有继承了object类的子类,以及该子类的子子孙孙类都能用不到object内的功能,称之为经典类
- ps:只有在python2中才区分经典类与新式类,python3中都是新式类
继承类的语法


class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass print(SubClass2.__bases__) print(SubClass1.__bases__) print(ParentClass2.__bases__)
View Code
我们可以调用__bases__()方法查看某一个类的父类
三、解决代码冗余实例


# 继承解决代码冗余实例 # 不适用继承定义老师和学生两个类 class Student: school = "SH" def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def student_info(self): print("学生%s:%s %s" % (self.name, self.age, self.gender)) class Teacher: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def teacher_info(self): print("老师%s %s %s" % (self.name, self.age, self.gender)) ## 可以看到,我们在定义老师和学生的类时,姓名、年龄、性别写了重复代码,需要优化 # 定义一个成员类来优化上述问题 class Member: school = "SH" def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(Member): # def __init__(self,name,age,gender): # self.name = name # self.age = age # self.gender = gender def student_info(self): print("学生%s:%s %s" % (self.name, self.age, self.gender)) class Teacher(Member): # def __init__(self,name,age,gender): # self.name = name # self.age = age # self.gender = gender def teacher_info(self): print("老师%s %s %s" % (self.name, self.age, self.gender)) # 生成两个对象 student1 = Student("zhangdada", 18, "male") teacher1 = Teacher("faxia", 19, "female") print(student1.__dict__) # {'gender': 'male', 'name': 'zhangdada', 'age': 18} print(teacher1.__dict__) # {'gender': 'female', 'name': 'faxia', 'age': 19}
View Code
- 在定义父类时,我们应该遵守先抽象,再继承的原则,即先抽离多个类的相同属性,再定义父类
- 在继承父类之后,查找属性的顺序为:对象自己—>生成对象的类—>父类
四、继承类后的属性查找顺序详解
继承父类分为两种形式,单个继承与多个父类继承,单个父类相对简单,多个父类需要考虑多种因素
1、单个父类继承下的属性查找
按照:对象—>子类—>父类—>父类的父类……的顺序逐个查找
2、继承多个父类的属性查找
1)首先了解两个概念:新式类/经典类、菱形继承/非菱形继承
- 但凡是继承了object类的子类,以及该子类的子子孙孙类都能用到object内的功能,称之为新式类;没有继承了object类的子类,以及该子类的子子孙孙类都能用不到object内的功能,称之为经典类。Python3中全部是新式类,因为都继承了object类
- 菱形继承即一个类继承了多个类,被继承的父类又继承了各自的父类,但是最终,这些继承的分支又回归为一点,继承了同一个类(object除外),这就是菱形继承,反之,则是非菱形继承
2)两种情况下的属性查找
- 非菱形继承下,无论是新式类还是经典类,都是按照:对象—》类A—》类B—》类C—》类E—》类G—》类D—》类F(—》object,新式类下最后检索object)
- 在菱形继承下,新式类遵循广度优先原则,先找每个分支,但是不会查找类G,直到所有的类中都没有找到,最后再找类G,然后是object;对象—》类A—》类B—》类C—》类E—》类D—》类F—》类G—》object
- 在菱形继承下,经典类按照深度优先,会将一条分支查找完毕,才会去查找下一条分支,也就是说会在类E后去查找类G,之后不再查找类G:对象—》类A—》类B—》类C—》类E—》类G—》类D—》类F
3)c3算法
其实,以上的查找顺序都是由c3算法得出,c3算法会得出一个mro字典,这个字典规定了属性查找的顺序,需要注意的是,查找的顺序都是以发起查找类为起点,无论查找到什么位置,顺序都不会改变


# c3会计算出一个mro列表 # super()得到一个特殊的对象,该对象访问的属性直接从父类中查找 class A: def test(self): print('A下的test') super().test() class B: def test(self): print('from B') class C(A,B): pass obj = C() # A下的test obj.test() # from B print(C.mro()) # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
View Code
上面的例子展示了c3算法下的mro字典,但是却有个坑,相信不少细心的童鞋看出来了,super()方法我们在后面会讲到,但是可以提前说明一下作用:自当前类为起点,到他的父类中搜索,也就是从A的父类中去搜索test(),但是A并没有继承任何父类,却是成功找到了test(),这是怎么回事?
我们查看结果可以看到,obj.test() 的结果为from B,这是B运行test()的结果,难道A将B当做了父类去检索?事实上确实如此,这里我们需要注意,因为是从C为起点开始查找,
所以查找顺序是C的mro字典:[<class ‘__main__.C’>, <class ‘__main__.A’>, <class ‘__main__.B’>, <class ‘object’>],字典中B在A的后面,所以super()将B当做了A的父类去查找,并最总找到B的test(),运行得到结果。
因为涉及到多父类继承,并且继承方式的不同,顺序也有区别,所以我们遇到上述情况时,可以查看mro列表,更好的理解父类间的查找顺序。
五、在子类派生的新方法中如何重用父类的功能
上面讲到子类可以继承父类的属性,但是子类还有自己的属性,在子类派生的新方法中如何重用父类的功能呢?下面两个方法可以实现
- 直接调用父类.函数(),与继承无关


# 在子类派生的新方法中如何重用父类的功能 ## 父类.函数(),与继承无关 class Member: school = "SH" def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(Member): def __init__(self,name,age,gender,courses=None): Member.__init__(name,age,gender) # 调用父类__init__()方法 if courses == None: courses = [] self.couurses = courses def student_info(self): print("学生%s:%s %s" % (self.name, self.age, self.gender)) class Teacher(Member): def __init__(self,name,age,gender,level): Member.__init__(name, age, gender) # 调用父类__init__()方法 self.level = level def teacher_info(self): print("老师%s %s %s" % (self.name, self.age, self.gender))
View Code
- 使用super()对象,super()对象会参照属性查找发起者的mro列表,去当前类的下一个类中找属性


# 在子类派生的新方法中如何重用父类的功能 ## 使用super()方法 class Member: school = "SH" def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(Member): def __init__(self,name,age,gender,courses=None): super().__init__(name, age, gender) # 使用super()方法 if courses == None: courses = [] self.couurses = courses def student_info(self): print("学生%s:%s %s" % (self.name, self.age, self.gender)) class Teacher(Member): def __init__(self,name,age,gender,level): super().__init__(name, age, gender) # 使用super()方法 self.level = level def teacher_info(self): print("老师%s %s %s" % (self.name, self.age, self.gender))
View Code
六、隐藏属性。隐藏属性的真正目的就是为了不让使用者在类外部的直接操作属性
在类中使用__属性名 可以隐藏属性————本质是将属性名进行了变形:_类名__属性名


class Student: def __init__(self,name,age,gender): self.__name = name # 将属性变形为:_Student__name self.age = age self.gender = gender def info(self): print(self.__name) # 将属性变形为:_Student__name print(self.age) print(self.gender) stu1 = Student("zhangdada",21,"male") stu1.name # 类外无法通过属性名直接访问
View Code
隐藏的属性有4个特点
- 并不是真正的隐藏,只是一种语法意义上的变形:_类名__属性名
- 该变形操作只在类定义阶段扫描语法时发生一次,类定义之后__开头的属性不会变形
- 该隐藏对外不对内。在类的内部,可以通过__属性名访问到
- 可以防止子类覆盖父类的某个属性
七、继承表达的关系:is-a及Mixins机制
这其实是一种使用类继承的思想,我们使用继承的初衷是为了避免类与类之间的代码冗余,但是过度滥用继承也会造成代码逻辑混乱,影响代码的可读性,那么我们在使用继承时,应该遵循什么原则呢?答案就是is-a原则
- is-a,顾名思义子类是父类的关系:人是动物,狗是动物…..需要注意,这里的是可以理解为被包含,如果仅是有联系就不应该使用继承,应该使用接下来要讲的组合,例如学生和课程,班级和老师等
Mixins机制
从我们的认知当中,一个事物应该只有一个is-a关系,你不能说一个事物是两种东西(你可能会举:人是生物,人也是动物的例子,但是动物本就包含在生物之下,我们说人是生物也包含了动物),也就是说我们在使用继承时,一般只应该有一个父类,但是继承多个类的情况也是存在的,如果不加以区别,将多个类都认为是一个类的父类,不仅会显得逻辑混乱,代码不够清晰,而且也会导致菱形继承等问题,所以python提供了Mixins机制解决这个问题。
举个栗子:我们有汽车、飞机、热气球三种类,他们都有一个父类交通工具,这三种类确实是交通工具,没有问题,但是如果我们的增加了需求,需要添加飞行这个类,怎么办?我们直接想到的办法是定义一个飞行类,然后让飞机与热气球直接继承飞行这个类,但是这样违背了is-a的原则,并且出现了继承多个类的原则。
我们注意到,飞行只是飞机与热气球的功能,我们需要的仅仅是功能的混合,而不是继承一个父类,这时,我们只需要定义一个以Mixins结尾的类,使用飞机与热气球继承这个类,表示这仅仅是功能混合,不是父类,需要注意,这个以Mixins结尾的类需要写在父类前面。


# 定义类:交通工具 class Vehicle: def __init__(self,name): self.name = name # 定义功能类:飞行 class FlyMixins: def fly(self): pass # 定义类:汽车 class Car(Vehicle): def __init__(self,name): super().__init__() # 定义类:飞机,继承父类Vehicle以及功能类FlyMixins class Plane(FlyMixins,Vehicle): def __init__(self,name): super().__init__(name) # 定义类:热气球,继承父类Vehicle以及功能类FlyMixins class Balloon(FlyMixins,Vehicle): def __init__(self,name): super().__init__(name)
View Code
八,组合
在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;而组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例


# 定义课程类 class Courses: def __init__(self,name,period,price): self.name = name self.period = period self.price = price def check_course(self): print(self.name) # 定义学生类 class Student(People): # 继承 def __init__(self,name,age,gender,courses=None): super().__init__(name,age,gender) if courses == None: courses = [] self.courses = courses def func1(self): pass # 生成对象学生 stu1 = Student("zhangyifan",20,"male") # 生成课程对象 course1 = Courses("Python","6month",10000) # 将课程对象当做数据属性传入学生对象,这就是组合 stu1.courses.append(course1) # 通过学生类访问课程属性, print(stu1.courses[0].name) # Python
View Code