面向对象之继承与派生

一、继承

1、什么是继承

继承是一种新建类的方式,新建的类称之为子类或派生类,继承的父亲称之为基类或超类。
- 在Python中,一个子类可以继承多个父类。
- 在其它语言中,一个子类只能继承一个父类。

2、继承的作用

减少代码的冗余

3、 如何实现继承

  1. 先确认谁是子类,谁是父类。

  2. 在定义类子类时,语法:

    class 子类名(父类名):
    
class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
    pass

class SubClass1(ParentClass1): #单继承,父类是ParentClass1,派生类是SubClass1
    pass

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

SubClass1.__base__  #__base__只查看从左到右继承的第一个子类
>>> (<class '__main__.ParentClass1'>,)
SubClass2.__bases__  #__bases__则是查看所有继承的父类
>>> (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

要找出类与类之间的继承关系,需要先抽象,再继承。

  • 抽取对象之间相似的部分,总结出类
  • 抽取类之间相似的部分,总结出父类。

抽象即总结相似之处,总结对象之间的相似之处得到类,总结类与类之间的相似之处就可以得到父类,如下图所示

在这里插入图片描述
基于抽象的结果,我们就找到了继承关系
在这里插入图片描述

问题: 代码冗余
# 老师类
class BjdxTeacher:
    school = 'Bjdxboy'

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

    # 老师修改分数
    def change_score(self):
        print(f'老师 {self.name} 正在修改分数...')


# 学生类
class BjdxStudent:
    school = 'oldboy'
    country = 'China'

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

    # 学生选择课程
    def choose_course(self):
        print(f'学生 {self.name} 正在选择课程...')


stu1 = BjdxStudent('baohan', 17, 'female')
print(stu1.school, stu1.name, stu1.age, stu1.sex)

tea1 = BjdxTeacher('sean', 40, 'male')
print(tea1.school, tea1.name, tea1.age, tea1.sex)

解决代码冗余问题:

class People:
    school='Bjdx'
    
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age
    
class BjdxStudent(People):
    def choose(self):
        print('%s is choosing a course' % self.name)

class BjdxTeacher(People):
    def teach(self):
        print('%s is teaching' % self.name)
        
        
teacher1=BjdxTeacher('sean','male',40)
print(teacher1.school,teacher1.name,teacher1.sex,teacher1.age)
>>>('Bjdx', 'sean', 'male', 40)

stu1 = BjdxStudent('baohan', 23, 'female')
print(stu1.school, stu1.name, stu1.age, stu1.sex)

注意: 程序的执行顺序是由上到下,父类必须定义在子类的上方。

二、在继承背景下,对象属性的查找顺序:

  1. 先从对象自己的名称空间中查找
  2. 对象中没有,从子类的名称空间中查找
  3. 子类中没有, 从父类的名称空间中查找,若父类没有,则会报错!
class Goo:  #父类
    x = 10
    pass
class Foo(Goo):  #子类
    # x = 100
    pass
foo_obj = Foo()
# foo_obj.x = 1000
print(foo_obj.x)

三、派生:

派生 指的是子类继承父类的属性与方法,并且派生出自己独有的属性与方法。
若子类中的方法名与父类的相同,优先使用子类的。

#父类
class Foo:
    def f1(self):
        print('from foo.f1...')
    def f2(self):  #self --->bar_obj
        print('from foo.f2...')
        #bar_obj.f1() ---> 对象自己找 ---> Bar ---> Foo
        self.f1()

#子类
class Bar(Foo):
    #重写
    def f1(self):
        print('from Bar.f1...')
    def func(self):
        print('from Bar.func...')

bar_obj = Bar()
bar_obj.f1()  #from Bar.f1...

bar_obj.f2()  #from foo.f2...
              #from Bar.f1...
问题:字类重写父类的__init__导致代码更加冗余:

class BjdxPeople:
    school = 'Bjdx'

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

class BjdxTeacher(BjdxPeople):

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

    def change_score(self):
        print(f'老师{self.name}修改分数...')

class BjdxStudent(BjdxPeople):
    def __init__(self,name,age,sex,girl):
        self.name = name
        self.age = age
        self.sex = sex
        self.girl = girl

    def choose_course(self):
        print(f'学生 {self.name} 选择课程...')

tea1 = BjdxTeacher('tank',66,'male',15000)
stu1 = BjdxStudent('baohan',18,'male','小花')

print(tea1.name, tea1.age, tea1.sex, tea1.sal)
print(stu1.name, stu1.age, stu1.sex, stu1.girl)

'''
解决问题: 子类重用父类的属性,并派生出新的属性。
    两种方式:
        1.直接引用父类的__init__为其传参,并添加子类的属性。
        2.通过super来指向父类的属性。
            - super()是一个特殊的类,调用super得到一个对象,该对象指向父类的名称空间。
            
        注意: 使用哪一种都可以,但不能两种方式混合使用。
'''

方式一:指名道姓地调用,(其实与继承是没有什么关系的)

class BjdxPeople:
    school = 'oldboy'

    def __init__(self, name, age, sex):  # self == tea1, name == 'tank', age == 17, sex == 'male'
        self.name = name
        self.age = age
        self.sex = sex


class BjdxTeacher(BjdxPeople):

    # tea1, 'tank', 17, 'male', 15000000
    def __init__(self, name, age, sex, sal):
        # self.name = name
        # self.age = age
        # self.sex = sex
        # 类调用类内部的__init__,只是一个普通函数
        # BjdxPeople.__init__(tea1, 'tank', 17, 'male')
        BjdxPeople.__init__(self, name, age, sex)
        self.sal = sal

    def change_score(self):
        print(f'老师 {self.name} 修改分数...')


class BjdxStudent(BjdxPeople):

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

    def choose_course(self):
        print(f'学生 {self.name} 选择课程...')


tea1 = BjdxTeacher('tank', 17, 'male', 15000)
print(tea1.name, tea1.age, tea1.sex, tea1.sal)


stu1 = BjdxStudent('baohan', 28, 'male', '小花')
print(stu1.name, stu1.age, stu1.sex, stu1.girl)
方式二:super()调用,此方式(严格)依赖于继承
super()的返回值是一个特殊的对象,该对象专门用来调用父类中的属性

#了解:在python2中, 需要super(自己的类名,self)

class BjdxPeople:
    school = 'Bjdx'
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

class BjdxTeacher:

    def __init__(self,name,age,sex,sal):

        super().__init__(name,age,sex)

        self.sal = sal

    def change_score(self):
        print(f'老师 {self.name} 修改分数...')

class BjdxStudent(BjdxPeople):

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

    def choose_course(self):
        print(f'学生 {self.name} 选择课程...')


tea1 = BjdxTeacher('tank', 17, 'male', 15000)
print(tea1.name, tea1.age, tea1.sex, tea1.sal)


stu1 = BjdxStudent('baohan', 28, 'male', '小花')
print(stu1.name, stu1.age, stu1.sex, stu1.girl)

四、经典类与新式类(了解)

  • 新式类:
    1.凡是继承object的类或子孙类都是新式类。
    2.在python3中所有的类都默认继承object。

  • 经典类:
    1.在python2中才会有经典类与新式类之分。
    2.在python2中,凡是没有继承object的类以及该类的子类,都是经典类。

五、菱形继承(钻石继承)

多继承情况下造成 “钻石继承”

mro的查找顺序:
    - 新式类:
        - 广度优先

    - 经典类:
        - 深度优先

注意:super()会严格地遵循mro列表从当前查找到的位置继续往后查找

在这里插入图片描述
在这里插入图片描述
在python3中提供了一个查找新式类查找顺序的内置方法.
mro(): 会把当前类的继承关系列出来。

mro查找顺序遵循如下三条准则:

1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类

原文地址:https://www.cnblogs.com/baohanblog/p/12143065.html