day26-继承和派生

2020-08-10  本文已影响0人  天行_b6d0

一、常用三连

  1. 什么是继承
    继承是一种新建类的方式,新建的类称之为子类,被继承的类称之为父类、超类、基类。
    在python中支持多继承
  2. 为什么要用继承
    子类可以遗传父类的属性,使用继承可以解决类与类之间代码的冗余问题。
  3. 如何实现继承
class Parent1:
    pass

class Parent2:
    pass

class Sub1(Parent1):  # 在类名后加上括号,里面放被继承类的类名
    pass

class Sub2(Parent1,Parent2):
    pass

print(Sub1.__bases__)  # 查看该类的父类
print(Sub2.__bases__)

二、在子类派生的新方法中重用父类的功能

method_1:指名道姓地调用某个类的函数
特点:不依赖于继承关系

class OldboyPeople:
    school = "oldboy"
    #             空对象,"洛根",22,'male'
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender

    def f1(self):
        print('1111111')

class Student(OldboyPeople):
    #            空对象,"洛根",22,'male',1001,"python全栈开发"
    def __init__(self,name,age,gender,stu_id,course):
        OldboyPeople.__init__(self,name,age,gender)  # OldboyPeople.__init__(空对象,"洛根",22,'male')
        self.stu_id = stu_id
        self.course = course


    def choose(self):
        print('%s 正在选课' %self.name)

    def f1(self):
        OldboyPeople.f1(self)
        print("22222")

class Teacher(OldboyPeople):
    def score(self,stu,num):
        stu.num = num


stu1=Student("洛根",22,'male',1001,"python全栈开发")
# tea1=Teacher("egon",18,'male',2000,10)


stu1.f1()

method_2:super()

调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找
>>> #A没有继承B
... class A:
...     def test(self):
...         super().test()
... 
>>> class B:
...     def test(self):
...         print('from B')
... 
>>> class C(A,B):
...     pass
... 
>>> C.mro() # 在代码层面A并不是B的子类,但从MRO列表来看,属性查找时,就是按照顺序C->A->B->object,B就相当于A的“父类”
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,<class ‘object'>]
>>> obj=C()
>>> obj.test() # 属性查找的发起者是类C的对象obj,所以中途发生的属性查找都是参照C.mro()
from B

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

三、属性查找

由对象引发的属性查找: 先从对象里面找,再去类里面找,再去父类里面找。
例一:

class Foo:
    def f2(self):
        print("Foo.f2")

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


class Bar(Foo):
    def f2(self):
        print("Bar.f2")


obj = Bar()
obj.f1()  # 输出的结果为:Foo.f1    Bar.f2

例二:父类如果不想让子类覆盖自己的方法,可以在方法名前加前缀__

class Foo:
    def __f2(self):  # _Foo__f2
        print("Foo.f2")

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


class Bar(Foo):
    def __f2(self):  # _Bar__f2
        print("Bar.f2")


obj = Bar()

obj.f1()

四、继承的实现原理

1、两个种类的类
新式类:但凡是继承了object类的子类,以该子类子子孙孙类都称之为新式类
经典类:没有继承了object类的子类,以该子类子子孙孙类都称之为经典类
在python3中全是新式类,只有在python2 中才有经典类,在python3中没有继承任何类的类会默认继承object类。

2、菱形问题
一个子类继承的多条件分支最终汇聚到一个非object类,在菱形继承下,新式类与经典类关于属性查找的方式不同。
新式类:广度优先
经典类:深度优先

例1:非菱形继承,经典类与新式类的属性查找顺序都一样


菱形继承1.png
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')


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


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

obj=A()
obj.test()

例2:菱形继承


菱形继承2.png
class G(object): # 在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')
    pass

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

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

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

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

obj=A()
obj.test()

print(A.mro())

五、Python Mixins机制

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

答案是有,我们用交通工具来举例:

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

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


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


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


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

但是如果民航飞机和直升机都各自写自己的飞行fly方法,又违背了代码尽可能重用的原则(如果以后飞行工具越来越多,那会重复代码将会越来越多)。

所以为了尽可能地重用代码,那就只好在定义出一个飞行器的类,然后让民航飞机和直升飞机同时继承交通工具以及飞行器两个父类,这样就出现了多重继承。这时又违背了继承必须是”is-a”关系。这个难题该怎么解决?
<<<<<<<<<>>>>>>>>>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,而不是一个飞行器。

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

六、组合

在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;而组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例

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()
上一篇 下一篇

猜你喜欢

热点阅读