27-继承与派生

一、继承的介绍

1.1 什么是继承

I:继承是一种创建新类的方式,新建的类可称为子类或派生类,父类又可称为基类或超类,子类会遗传父类的属性

II:需要注意的是:python支持多继承

在Python中,新建的类可以继承一个或多个父类

class Parent1(object):
    x=1111

class Parent2(object):
    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'>)

print(Sub1.x)               # 111

ps1: 在python2中有经典类与新式类之分

新式类:继承了object类的子类,以及该子类的子类子子类……

经典:没有继承object类的子类,以及该子类的子类子子类……

ps2:在python3中没有继承任何类,那么会默认继承object类,所以python3中所有的类都是新式类

print(Parent1.__bases__)                # (<class 'object'>,)
print(Parent2.__bases__)                # (<class 'object'>,)

III:python的多继承

  优点:子类可以同时遗传多个父类的属性,最大限度地重用代码

  缺点:

    1、违背人的思维习惯:继承表达的是一种什么"是"什么的关系

    2、代码可读性会变差

    3、不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差,

  如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用Mixins

 

1.2 为何要继承

用来解决类与类之间代码冗余问题

1.3 如何实现继承

示范一:类与类之间存在冗余问题

# 示范1:类与类之间存在冗余问题
class Student:
    school='OLDBOY'

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

    def choose_course(self):
        print('学生%s 正在选课' %self.name)


class Teacher:
    school='OLDBOY'

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

    def score(self):
        print('老师 %s 正在给学生打分' %self.name)

示范二:基于继承解决冗余问题

class OldboyPeople:
    school = 'OLDBOY'

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


class Student(OldboyPeople):
    def choose_course(self):
        print('学生%s 正在选课' % self.name)

stu_obj = Student('lili', 18, 'female')
print(stu_obj.__dict__)
print(stu_obj.school)
stu_obj.choose_course()


class Teacher(OldboyPeople):
    #           老师的空对象,'egon',18,'male',3000,10
    def __init__(self, name, age, sex, salary, level):
        # 指名道姓地跟父类OldboyPeople去要__init__
        OldboyPeople.__init__(self,name,age, sex)
        self.salary = salary
        self.level = level

    def score(self):
        print('老师 %s 正在给学生打分' % self.name)

tea_obj=Teacher('egon',18,'male',3000,10)

tea_obj.score()

 二、继承下的属性查找

2.1 单继承下的属性查找

# 单继承背景下的属性查找
# 示范一:
class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.f1() # obj.f1()

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

obj=Bar()
obj.f2()
# Foo.f2
# Foo.f1



# 示范二:
class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        Foo.f1(self) # 调用当前类中的f1

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

obj=Bar()
obj.f2()
# Foo.f2
# Foo.f1

# 示范三:
class Foo:
    def __f1(self): # _Foo__f1
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.__f1() # self._Foo__f1,# 调用当前类中的f1

class Bar(Foo):
    def __f1(self): # _Bar__f1
        print('Bar.f1')

obj=Bar()
obj.f2()
# Foo.f2
# Foo.f1

 

2.2 多继承下的属性查找

多继承可分为菱形问题与非菱形问题

2.2.1 mro方法

 在python中,新式类可以直接使用mro方法来查看mro列表,每个类均参照其mro列表来查看属性

class A(object):
    # def test(self):
    #     print('from A')
    pass

class B(A):
    def test(self):
        print('from B')
    pass

class C(A):
    # def test(self):
    #     print('from C')
    pass

class D(C,B):
    # def test(self):
    #     print('from D')
    pass

print(D.mro()) # 类D以及类D的对象访问属性都是参照该类的mro列表
# [<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

# obj = D()
# obj.test()

# print(D.test)

print(C.mro()) # 类C以及类C的对象访问属性都是参照该类的mro列表
# [<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
# c=C()
# c.test()

# 总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的mro

2.2.2 非菱形多继承

如果多继承是非菱形继承,经典类与新式的属性查找顺序一样:

    都是一个分支一个分支地找下去,然后最后找object

 

class E:
    # def test(self):
    #     print('from E')
    pass

class F:
    def test(self):
        print('from F')


class B(E):
    # def test(self):
    #     print('from B')
    pass

class C(F):
    # def test(self):
    #     print('from C')
    pass

class D:
    def test(self):
        print('from D')


class A(B, C, D):
    # def test(self):
    #     print('from A')
    pass

# 新式类
# print(A.mro()) # A->B->E->C->F->D->object

obj = A()
obj.test() # 结果为:from F

2.2.3 菱形问题多继承

如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样:
经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类)
新式类:广度优先,会在检索最后一条分支的时候检索大脑袋
 
class G: # 在python2中,未继承object的类及其子类,都是经典类
    # def test(self):
    #     print('from G')
    pass

class E(G):
    # def test(self):
    #     print('from E')
    pass

class F(G):
    def test(self):
        print('from F')

class B(E):
    # def test(self):
    #     print('from B')
    pass

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

# 新式类
# print(A.mro()) # A->B->E->C->F->D->G->object

# 经典类:A->B->E->G->C->F->D
obj = A()
obj.test() 

 

三、Python的mixins机制

3.1 mixins机制介绍

​ 一个子类可以同时继承多个父类,这样的设计常被人诟病,一来它有可能导致可恶的菱形问题,二来在人的世界观里继承应该是个”is-a”关系。 比如轿车类之所以可以继承交通工具类,是因为基于人的世界观,我们可以说:轿车是一个(“is-a”)交通工具,而在人的世界观里,一个物品不可能是多种不同的东西,因此多重继承在人的世界观里是说不通的,它仅仅只是代码层面的逻辑。不过有没有这种情况,一个类的确是需要继承多个类呢?

mixins机制核心:就是在多继承背景下尽可能地提升多继承的可读性(让多继承满足人的思维习惯=》什么"是"什么

 

3.2 mixins一般使用

民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示我们把飞行功能放到交通工具这个父类中是不合理的

class Vehicle:  # 交通工具
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


class CivilAircraft(Vehicle):  # 民航飞机
    pass


class Helicopter(Vehicle):  # 直升飞机
    pass


class Car(Vehicle):  # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
    pass

Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,如下

class Vehicle:  # 交通工具
    pass


class FlyableMixin:
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
    pass


class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
    pass


class Car(Vehicle):  # 汽车
    pass

# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路

可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,而不是Flyable,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),这种命名方式就是用来明确地告诉别人(python语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。

 

3.3 mixins机制使用注意

使用Mixin类实现多重继承要非常小心

  • 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
  • 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
  • 然后,它不依赖于子类的实现
  • 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)

Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差,并且更恶心的是,在继承的层级变多时,代码阅读者在定位某一个方法到底在何处调用时会晕头转向,如下

class Displayer:
    def display(self, message):
        print(message)


class LoggerMixin:
    def log(self, message, filename='logfile.txt'):
        with open(filename, 'a') as fh:
            fh.write(message)

    def display(self, message):
        super().display(message) # super的用法请参考下一小节
        self.log(message)


class MySubClass(LoggerMixin, Displayer):
    def log(self, message):
        super().log(message, filename='subclasslog.txt') 


obj = MySubClass()
obj.display("This string will be shown and logged in subclasslog.txt")


# 属性查找的发起者是obj,所以会参照类MySubClass的MRO来检索属性
#[<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>]

# 1、首先会去对象obj的类MySubClass找方法display,没有则去类LoggerMixin中找,找到开始执行代码
# 2、执行LoggerMixin的第一行代码:执行super().display(message),参照MySubClass.mro(),super会去下一个类即类Displayer中找,找到display,开始执行代码,打印消息"This string will be shown and logged in subclasslog.txt"
# 3、执行LoggerMixin的第二行代码:self.log(message),self是对象obj,即obj.log(message),属性查找的发起者为obj,所以会按照其类MySubClass.mro(),即MySubClass->LoggerMixin->Displayer->object的顺序查找,
# 在MySubClass中找到方法log,开始执行super().log(message, filename='subclasslog.txt'),super会按照MySubClass.mro()查找下一个类,在类LoggerMixin中找到log方法开始执行,最终将日志写入文件subclasslog.txt

 

3.4 总结

问:多继承到底要不用?

答:要用,但是规避几点问题

1、继承结构尽量不要过于复杂

2、推荐使用mixins机制:在多继承的背景下满足继承的什么"是"什么的关系

 

四、派生与方法重用

在子类派生的新方法中如何重用父类的功能?

4.1 子类派生新方法中重用父类功能的两个方式

方法一:“指名道姓”地调用某一个类的函数

# 方式一:指名道姓调用某一个类下的函数=》不依赖于继承关系
class OldboyPeople:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

    def f1(self):
        print('%s say hello' %self.name)


class Teacher(OldboyPeople):
    def __init__(self,name,age,sex,level,salary):
        OldboyPeople.__init__(self,name,age,sex)

        self.level = level
        self.salary=salary

tea_obj=Teacher('egon',18,'male',10,3000)
print(tea_obj.__dict__)

方法二:super()

调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找

# 方式二:super()调用父类提供给自己的方法=》严格依赖继承关系
#        调用super()会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro,去当前类的父类中找属性
class OldboyPeople:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

    def f1(self):
        print('%s say hello' %self.name)


class Teacher(OldboyPeople):
    def __init__(self,name,age,sex,level,salary):
        # super(Teacher,self).__init__(name,age,sex)  Python2中需要传入类名与self,python3中简化为无须传参
        super().__init__(name,age,sex) # 调用的是方法,自动传入对象

        self.level = level
        self.salary=salary

# print(Teacher.mro())
tea_obj=Teacher('egon',18,'male',10,3000)
print(tea_obj.__dict__)

PS:在Python2中super的使用需要完整地写成super(自己的类名,self) ,而在python3中可以简写为super()

4.2  super()的查找顺序

两种方式的区别:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找

# super()案例
class A:
    def test(self):
        print('from A')
        super().test()

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

class C(A,B):
    pass

obj=C()
obj.test()              # from A   from B

print(C.mro())          # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

obj.test()首先找到A下的test方法,执行super().test()会基于MRO列表(以C.mro()为准)当前所处的位置继续往后查找(),然后在B中找到了test方法并执行。

关于在子类中重用父类功能的这两种方式,使用任何一种都可以,但是在最新的代码中还是推荐使用super()

 

五、 组合

在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:

继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;

组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例

class Course:
    def __init__(self,name,period,price):
        self.name=name
        self.period=period
        self.price=price
    def tell_info(self):
        print('<%s %s %s>' %(self.name,self.period,self.price))

class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day
    def tell_birth(self):
       print('<%s-%s-%s>' %(self.year,self.mon,self.day))

class People:
    school='清华大学'
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age

#Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
class Teacher(People): #老师是人
    def __init__(self,name,sex,age,title,year,mon,day):
        super().__init__(name,age,sex)
        self.birth=Date(year,mon,day) #老师有生日
        self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
    def teach(self):
        print('%s is teaching' %self.name)


python=Course('python','3mons',3000.0)
linux=Course('linux','5mons',5000.0)
teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)

# teacher1有两门课程
teacher1.courses.append(python)
teacher1.courses.append(linux)

# 重用Date类的功能
teacher1.birth.tell_birth()

# 重用Course类的功能
for obj in teacher1.courses: 
    obj.tell_info()

 此时对象teacher1集对象独有的属性、Teacher类中的内容、Course类中的内容于一身(都可以访问到),是一个高度整合的产物

 

 



posted @ 2020-04-09 20:28  Jil-Menzerna  阅读(168)  评论(0编辑  收藏  举报