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