第四章 继承

1 什么是继承

所谓继承:是指可以让某个类型的对象获得另一个类型的对象的属性的方法。

继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的新类称为子类派生类,被继承的类称为基类父类超类

继承可以减少代码冗余,提高代码重用性。比如第一个练习的两个游戏角色就有大量的冗余代码

2 继承的实现

class Animal():
    life_condition = '氧气'

    def __init__(self, hobby):
        self.hobby = hobby

    def run(self):
        print('动物正在跑...')


class Dog(Animal):
    pass

class Pig(Animal):
    pass

对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。PigDog类似。

子类可以获得父类的全部功能,由于Animial实现了run()方法,因此,DogPig作为它的子类,什么事也没干,就自动拥有了run()

3 属性重写

子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

# 定义父类
class Hero:
    def __init__(self,nickname,aggressivity,life_value):
        self.nickname=nickname
        self.aggressivity=aggressivity
        self.life_value=life_value

    def move_forward(self):
        print('%s move forward' %self.nickname)

    def move_backward(self):
        print('%s move backward' %self.nickname)

    def move_left(self):
        print('%s move forward' %self.nickname)

    def move_right(self):
        print('%s move forward' %self.nickname)

    def attack(self,enemy):
        enemy.life_value-=self.aggressivity


# 定义子类

class Garen(Hero):
    pass

class Riven(Hero):
    camp='Noxus' # 定义新的类属性
    def __init__(self,nickname,aggressivity,life_value,skin):
        Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能
        self.skin=skin #新的实例属性
    def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
        Hero.attack(self,enemy) #调用功能
        print('from riven')
    def fly(self): #在自己这里定义新的
        print('%s is flying' %self.nickname)


g1=Garen('小米粒',100,300)
r1=Riven('豆芽',57,200, '超级战甲')

print(g1.life_value)
r1.attack(g1)
print(g1.life_value)

4 组合

解决软件重用性的重要方式除了继承之外还有另外一种方式,即:组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

class Equip: #武器装备类
     def fire(self):
         print('release Fire skill')

class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类
     camp='Noxus'
     def __init__(self,nickname):
         self.nickname=nickname
         self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性

r1=Riven('小辣椒')
r1.equip.fire() #可以使用组合的类产生的对象所持有的方法

合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同

当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好

1.继承的方式

过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人

2.组合的方式

用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和PHP课程,教授有学生s1、s2、s3...

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

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 Teacher(People):
    def __init__(self,name,age,sex,job_title):
        People.__init__(self,name,age,sex)
        self.job_title=job_title
        self.course=[]
        self.students=[]


class Student(People):
    def __init__(self,name,age,sex):
        People.__init__(self,name,age,sex)
        self.course=[]


t1=Teacher('发哥',48,'male','猥琐闷骚油腻老教授')
s1=Student('小翠',18,'female')

python=Course('python','3mons',3000.0)
php=Course('php','3mons',3000.0)

#为教授t1和学生s1添加课程
t1.course.append(python)
t1.course.append(linux)
s1.course.append(python)

#为老师egon添加学生s1
t1.students.append(s1)


#使用
for obj in t1.course:
    obj.tell_info()

5 继承树

一个类,如果没有指定父类,默认会继承object

class Animal():
    pass

# 等同于
class Animal():
    pass

继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树

注意:属性查找

当引用对象中某个属性的时候,先从实例属性中找,再从类属性中找,再从父类属性中找,一直到object

6 多继承

在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类

# Dog 类 同时继承三个父类
class Dog(Mammal, Runnable, Vocalable):
    pass

7 菱形继承问题和MRO列表

Python中既然可以多继承,必然会出现属性的查找问题: 当本类中没有要访问的属性时,到底要从哪个属性中查找呢?

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

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

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

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

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

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test() # 结果是多少

在Python3中,查找属性,会按照广度优先的方式查找

类继承顺序: F->D->B->E->C->A

可以通过以下方法 获取一个方法的解析顺序,我们称之为MRO列表

F.mro()  
# 或者
F.__mro__()

如果上面的程序中的 C类不再继承A类,解析顺序会发生变化

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

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

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

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

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

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test() # 结果是多少

类继承顺序: F->D->B->A->E->C

关于MRO列表

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则: 1.子类会先于父类被检查 2.多个父类会根据它们在列表中的顺序被检查 3.如果对下一个类存在两个合法的选择,选择第一个父类

8 子类使用父类属性(方法)

8.1 使用父类类名

class Vehicle: #定义交通工具类
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('开动啦...')

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        Vehicle.__init__(self,name,speed,load,power)  # 调用父类方法
        self.line=line

    def run(self):
        print('地铁%s号线欢迎您' %self.line) # 调用父类方法
        Vehicle.run(self)

line13=Subway('上海地跌','280m/s','1000人/箱','电',13)
line13.run()

8.2 super()关键字

class Vehicle: #定义交通工具类
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('开动啦...')

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print('地铁%s号线欢迎您' %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜单车
    pass

line13=Subway('上海地铁','280m/s','1000人/箱','电',13)
line13.run()

8.3 两者的区别

即使没有直接继承关系,super仍然会按照mro继续往后查找

#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print('from B')
class C(A,B):
    pass

c=C()
c.test() #打印结果:from B


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

两者区别

# 通过类名
class A:
    def __init__(self):
        print('A的构造方法')
class B(A):
    def __init__(self):
        print('B的构造方法')
        A.__init__(self)


class C(A):
    def __init__(self):
        print('C的构造方法')
        A.__init__(self)


class D(B,C):
    def __init__(self):
        print('D的构造方法')
        B.__init__(self)
        C.__init__(self)

    pass
f1=D() #A.__init__被重复调用
'''
D的构造方法
B的构造方法
A的构造方法
C的构造方法
A的构造方法
'''


#使用super()
class A:
    def __init__(self):
        print('A的构造方法')
class B(A):
    def __init__(self):
        print('B的构造方法')
        super(B,self).__init__()


class C(A):
    def __init__(self):
        print('C的构造方法')
        super(C,self).__init__()


class D(B,C):
    def __init__(self):
        print('D的构造方法')
        super(D,self).__init__()

f1=D() #super()会基于mro列表,往后找
'''
D的构造方法
B的构造方法
C的构造方法
A的构造方法
'''

当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次

注意注意注意:

使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表

9 抽象类

9.1 什么是抽象类

java一样,python也有抽象类的概念但是需要借助模块实现

抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化

9.2 抽象类的作用

​ 如果说是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆中抽取相同的内容而来的,内容包括数据属性和函数属性。

​ 比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。*

​ 从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

  从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。

抽象类可以实现归一化设置

9.3 归一化

就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。

归一化的好处在于:

  1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

  2. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合

    2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。

    2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样

9.3 抽象类的实现

import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def read(self):
        '子类必须定义读功能'
        pass

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

10 接口 Interface

接口是也是一种实现归一化的方式,比抽象类更加抽象,接口中所有方法都必须是抽象方法

但是python并没有实现接口的功能,也没有内建模块实现接口的功能

如果非要去模仿接口的概念

可以借助第三方模块:

http://pypi.python.org/pypi/zope.interface

11 相关函数

isinstance(obj,cls) 检查是否obj是否是类 cls 的对象

class Foo(object):
     pass

obj = Foo()

isinstance(obj, Foo)
isinstance(obj, object)

issubclass(sub, super) 检查sub类是否是 super 类的派生类(子类)

class Foo(object):
     pass

class Bar(Foo):
     pass

issubclass(Bar, Foo)

results matching ""

    No results matching ""