面向对象之继承

一、继承的概念算法

继承是一种新建类的方式,新建类能够建立一个或多个父类,父类又能够称之为基类或超类,新建的类称之为子类或派生类,继承描述的是一种遗传关系,子类能够重用父类的属性和方法。使用继承能够减小类与类之间代码冗余的问题。编程

在 Python 中类的继承分为单继承和多继承编程语言

class ParentClass1():   # 定义父类
    pass

class ParentClass2():   # 定义父类
    pass

class SubClass1(ParentClass1):  # 定义子类继承父类, 单继承
    pass

class SubClass2(ParentClass1, ParentClass2):    # Python支持多继承, 用逗号分隔开多个继承的类
    pass

二、查看全部继承的父类:__bases__ide

class ParentClass1(): pass

class ParentClass2(): pass

class SubClass1(ParentClass1): pass

class SubClass2(ParentClass1, ParentClass2): pass

print(SubClass1.__bases__) print(SubClass2.__bases__) print(ParentClass1.__bases__) print(ParentClass2.__bases__) # 运行结果
(<class '__main__.ParentClass1'>,) (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>) (<class 'object'>,) (<class 'object'>,)
__bases__

若是没有指定父类,Python3 会默认继承 object 类,object 是全部类的父类函数

新式类:但凡继承 object 类的子类,以及该子类的子子类....,都称之为新式类spa

经典类:没有继承 object 类的子类,以及该子类的子子类....,都称之为经典类code

只有在 Python2 中才区分新式类和经典类,Python3 所有默认是新式类对象

三、继承与抽象(先抽象后继承)blog

抽象:抽取类似的部分(也就是提取一类事物的特色,范围愈来愈大,共性愈来愈少),是从大范围到小范围的过程继承

继承:是基于抽象的过程,经过编程语言去实现它,确定是先经历抽象这个过程,才能经过继承的方式去表达出抽象的结构,是从小范围到大范围的过程

四、派生

  1)在父类的基础上产生子类,产生的子类就叫作派生类

  2)父类中没有的方法,在子类中有,这样的方法就叫作派生方法

  3)父类中有个方法,子类中也有这个方法,就叫作方法的重写(就是把父类里的方法重写了)

五、注意的几个概念

  1)子类可使用父类的全部属性和方法

  2)若是子类有本身的方法,就执行本身的;若是子类没有本身的方法,就会找父类的

  3)若是子类里面没有找到,父类里也没有找到,就会报错

六、在子类派生出新的功能中如何重用父类的功能

如今我定义一个学生类,隶属于学校 “湫兮如风” ,学生有姓名,年龄,性别,还能够选课;再建立教师类,也隶属于学校 “湫兮如风” ,教师有姓名,年龄,性别,等级,工资,教师能够给学生打分

class Student(): school = '湫兮如风学院'

    def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def choose_course(self): print('%s正在选课' %self.name) class Teacher(): school = '湫兮如风学院'

    def __init__(self, name, age, gender, level, salary): self.name = name self.age = age self.gender = gender self.level = level self.salary = salary def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num
View Code

可是这样写有不少重复的代码,分析一下条件,教师和学生都属于学院的人,因而能够定义出一个父类让教师和学生去继承这个父类

class People(): school = '湫兮如风学院'

class Student(People): def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def choose_course(self): print('%s正在选课' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): self.name = name self.age = age self.gender = gender self.level = level self.salary = salary def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num
View Code

这样的处理还不够完全,由于还有 __init__ 里面还有部分重复的代码,能够将这些重复的代码放入父类,可是教师类中还有等级和工资没法处理,这两个对于父类来讲就是多出来的,会报错

class People(): school = '湫兮如风学院'

    def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在选课' %self.name) class Teacher(People): def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000) # 运行
TypeError: __init__() takes 4 positional arguments but 6 were given
View Code

能够在教师类中派生新的 __init__ ,将教师的姓名,年龄,性别,等级,工资写入,但这又回到了上一步,又有了重复的代码,这就引入了须要解决的问题:在子类派生出新的功能中如何重用父类的功能?

在函数中咱们介绍了调用的概念,在父类中有这个功能,在子类中不想再写,就能够调用这个功能

class People(): school = '湫兮如风学院'

    def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在选课' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): # 注意: 这里不能使用self去调用__init__, 这样调用的永远是本身的__init__,
        # 由于这里的self是Teacher的一个对象, 就是tea, tea先从本身去找__init__, 没有找到
        # 再到Teacher中找__init__, 找到了, 因而永远在调用本身的__init__, 没法访问到父类的__init__
        # 因而用类名去调, 这实际上就是一个普通函数, 没有自动传值的功能, 须要将参数一个很多的传入
        People.__init__(self, name, age, gender) self.level = level self.salary = salary def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000)
View Code

这是第一种方式,指名道姓的访问某一个类中的函数,这实际上与面向对象无关,即使是 Teacher 和 Person 没有继承关系,也可以调用

在讲第二种方式以前,须要先了解在继承背景下,经典类与新式类的属性查找顺序

首先是单继承,在单继承背景下,不管是新式类仍是经典类属性查找顺序都同样,都是先从对象中查找,对象中没有到类中,类中没有再到父类中。

而在多继承背景下,若是一个子类继承多个父类,但这些父类没有再继承同一个非 object 的父类,在这种状况下,新式类仍是经典类属性查找顺序也是同样的,先在对象中查找,对象中没有再到对象所在的类中,再没有就会按照从左到右的顺序一个父类一个父类的找下去

因此这里的查找顺序是:obj ---> A ---> B ---> E ---> C ---> F ---> D,D中没有再到 object 中查找,在这里 object 不在讨论范围以内,好比要查找 name 属性,object类 中不可能会去定义一个 name 属性。下面是代码实现

class E(): x = 'E'
    pass

class F(): X = 'F'
    pass

class B(E): # x = 'B'
    pass

class C(F): x = 'C'
    pass

class D(): x = 'D'
    pass

class A(B, C, D): # x = 'A'
    pass obj = A() print(obj.x)
View Code

还有一种状况,被称之为 “菱形继承” ,能够抽象的想像到,最终的父类是汇聚到一点,继承了同一个父类,而且这个父类也不是 object 类。在这种状况下,新式类和经典类的属性查找顺序不一样。

在新式类中,是按照广度优先查找,即:obj ---> A ---> B ---> E ---> C ---> F ---> D ---> G

在经典类中,是按照深度优先查找,即:obj ---> A ---> B ---> E ---> G ---> C ---> F ---> D

对于定义的每个类,Python 会计算出一个方法解析顺序(MRO)列表,这个 MRO 列表就是用于保存继承顺序的一个列表

class G(): x = 'G'
    pass

class E(G): x = 'E'
    pass

class F(G): X = 'F'
    pass

class B(E): x = 'B'
    pass

class C(F): x = 'C'
    pass

class D(G): x = 'D'
    pass

class A(B, C, D): x = 'A'
    pass obj = A() # print(obj.x)
print(A.mro()) # 运行结果
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
新式类查找顺序及MRO

为了实现继承,Python 会在 MRO 列表上从左到右开始查找父类,直到找到第一个匹配这个属性的类为止,而这个 MRO 列表的构造是经过一个 C3 线性化算法来实现的。咱们不去深究这个算法的数学原理,它实际上就是合并全部父类的 MRO 列表并遵循以下三条准则:

  1)子类会先于父类被检查

  2)若是有多个父类,则根据继承语法在列表内的书写顺序被检查

  3)若是多个类继承了同一个父类,则子类中只会选取继承语法括号中的第一个父类

了解了以上知识点,如今继续来讲在子类派生出的新功能中如何重用父类功能的方式二

有一个叫作 super 的内置函数,调用该函数会获得一个特殊的对象,该对象是专门用来访问父类中属性和方法

class People(): school = '湫兮如风学院'

    def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(People): def choose_course(self): print('%s正在选课' %self.name) class Teacher(People): def __init__(self, name, age, gender, level, salary): super(Teacher, self).__init__(name, age, gender) self.level = level self.salary = salary def score(self, stu, num): print('教师%s给学生%s打%s分' %(self.name, stu.name, num)) stu.num = num stu = Student('qiu', 22, 'male') tea = Teacher('xi', 20, 'male', 10, 3000)
super

强调:super 会严格参照类的 MRO 列表依次查找属性

这是子类在类里面调用父类方法,使用 super(子类类名,self).方法名() 或 super().__init__(参数),在 Python3 中 super 能够不传参数,Python2 中必需要传参数

相关文章
相关标签/搜索