Python面向对象三要素-继承(Inheritance)
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.继承概述
1>.基本概念
前面我们学习了Python的面向对象三要素之一,封装。今天我们来学习一下继承(Inheritance)
人类和猫类都继承自动物类。
个体继承自父类,继承了父类的一部分特征,但也可以有自己的个性。
再面向对象的世界中,从父类继承,就可以直接拥有父类的属性方法,这样可以减少代码,多复用。子类可以定义自己的属性和方法。
2>.看一个不用继承的例子
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6
7 classAnimal:8 defshout(self):9 print("Animal shouts")10
11
12 classCat:13 defshout(self):14 print("Cat shouts")15
16
17 a =Animal()18 a.shout()19
20 c =Cat()21 c.shout()22
23
24
25 #以上代码执行结果如下:
26 Animal shouts27 Cat shouts
3>. 使用继承的方式改良上一个不用继承的案例
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6
7 classAnimal:8 def __init__(self,name):9 self._name =name10
11 def shout(self): #定义一个通用的吃方法
12 print("{} shouts".format(self.__class__.__name__))13
14 @property15 defname(self):16 returnself._name17
18 classCat(Animal):19 pass
20
21 classDog(Animal):22 pass
23
24 a = Animal("monster")25 a.shout()26
27 cat = Cat("Kitty")28 cat.shout()29 print(cat.name)30
31 dog = Dog("二哈")32 dog.shout()33 print(dog.name)34
35
36 #以上代码执行结果如下:
37 Animal shouts38 Cat shouts39 Kitty40 Dog shouts41 二哈
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6 classDocument:7 def __init__(self,content):8 self.content =content9
10 def print(self): #基类中只定义,不实现的方法,称为“抽象方法”。在python中,如果采用这种方式定义的抽象方法,子类可以不实现,知道子类使用该方法的时候才报错。
11 """
12 基类提供的方法可以不具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。13 """
14 raiseNotImplementedError()15
16 classWord(Document):17 pass
18
19 classPdf(Document):20 pass
Python中抽象方法的案例
4>.总结
通过上例可以看出,通过继承,猫类,狗类不用写代码,直接继承了父类的属性和方法。
继承:
class Cat(Animal)这种形式就是从父类继承,括号中写上继承的类的列表。
继承可以让子类从父类获取特征(属性和方法)
父类:
calss Animal就是Cat和Dog的父类,也称为基类,超类。
子类:
Cat就是Animal的子类,也成为派生类。
二.继承定义
1>.继承使用格式
通过上面的案例,相比大家也可以总结出来继承的使用格式:
class 子类名(基类1[,基类2,...])
和C++一样,Python也支持多继承,继承也可以多级。
2>.在Python3中,object类是所有对象的根基类
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6
7 classA:8 pass
9
10 #如果类定义时,没有基类列表,等同于继承自object。
11 classA(object):12 pass
13
14
15 """
16 注意,上例在Python2中,两种写法时不同的。17 Python支持多继承,继承也可以多级。18 """
3>.查看继承的特殊属性和方法
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6
7 classA:8 pass
9
10 print(A.__base__) #类的基类。
11 print(A.__bases__) #类的基类元组。
12 print(A.__mro__) #显示方法查找顺序,基类的元组。
13 print(A.mro()) #同上,返回列表。
14 print(A.__subclasses__()) #类的子类列表。
15
16
17
18 #以上代码执行结果如下:
19
20 (,)21 (, )22 [, ]23 []
三.继承中的访问控制
1>.代码案例
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6 """
7 从父类继承,自己没有的,就可以到父类中找。8 私有的都是不可以访问的,但是本质上依然是改了名称放在这个属性所在类的实例"__dict__"中。9 知道这个新名称就可以直接找到这个隐藏的变量,这是个黑魔法技巧,慎用。10 """
11
12 classAnimal:13 __COUNT = 100
14 HEIGHT =015
16 def __init__(self,age,weight,height):17 self.__COUNT += 1
18 self.age =age19 self.__weight =weight20 self.HEIGHT =height21
22 defeat(self):23 print("{} eat".format(self.__class__.__name__))24
25 def __getweight(self):26 print(self.__weight)27
28 @classmethod29 defshowcount1(cls):30 print(cls)31 print(cls.__dict__)32 print(cls.__COUNT)33
34 @classmethod35 def __showcount2(cls):36 print(cls.__COUNT)37
38 defshowcount3(self):39 print(self.__COUNT)40
41 classCat(Animal):42 NAME = 'CAT'
43 __COUNT = 200
44
45
46 c = Cat(3,5,15)47 c.eat()48 print(c.HEIGHT)49 #print(c.__COUNT) #不能访问,因为它属于私有变量,该私有变量python内部做了处理,变量名称被更改。
50
51 #print(c.__getweight()) #同上,这是私有方法,该私有方法python内部做了处理,变量名称被更改。
52
53 c.showcount1()54
55 #c.__showcount2() #无法直接访问父类的私有方法,如果你非要访问的话,可以使用“c._Animal__showcount2()”
56
57 c.showcount3()58
59 print(c._Cat__COUNT)60
61 print(c._Animal__COUNT)62
63 print(c.NAME)64
65 print("{}".format(Animal.__dict__))66 print("{}".format(Cat.__dict__))67 print(c.__dict__)68 print(c.__class__.mro())69
70
71
72 #以上代码执行结果如下:
73 Cat eat74 15
75
76 {'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}77 100
78 101
79 200
80 101
81 CAT82 {'__module__': '__main__', '_Animal__COUNT': 100, 'HEIGHT': 0, '__init__': , 'eat': , '_Animal__getweight': , 'showcount1': , '_Animal__showcount2': , 'showcount3': , '__dict__': , '__weakref__': , '__doc__': None}83 {'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}84 {'_Animal__COUNT': 101, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15}85 [, , ]
2>.总结
继承时,公有的,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可直接访问,但私有变量所在的类内的方法中可以访问这个私有变量。
Python通过自己一套实现,实现和其它语言一样的面向对象的继承机制。
实例属性查找顺序:
实例的 "__dict__" ===> "类__dict__" ===> "父类__dict"
如果搜索这些地方后没有找到就会抛出异常,先找到就立即返回了。
四.方法的重写,覆盖override
1>.super()可以访问到父类的类属性
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6 classAnimal:7 defshout(self):8 print("Animal shouts")9
10 classCat(Animal):11 #def shout(self): #覆盖了父类方法
12 #print("miao")
13
14 def shout(self): #覆盖了自身的方法,显式调用了父类的方法
15 print(super())16 print(super(Cat,self))17 print(super(self.__class__,self))18
19 super().shout()20 super(Cat,self).shout() #等价于super()
21 self.__class__.__base__.shout(self)22
23 a =Animal()24 a.shout()25
26 c =Cat()27 c.shout()28
29 print(a.__dict__)30 print(c.__dict__)31 print(Animal.__dict__)32 print(Cat.__dict__)33
34
35
36
37 #以上代码执行结果如下:
38 Animal shouts39 , >
40 , >
41 , >
42 Animal shouts43 Animal shouts44 Animal shouts45 {}46 {}47 {'__module__': '__main__', 'shout': , '__dict__': , '__weakref__': , '__doc__': None}48 {'__module__': '__main__', 'shout': , '__doc__': None}
2>.类方法和静态方法覆盖
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6 """
7 这些方法都可以覆盖,原理都一样,属性字典的搜索顺序。8 """
9
10 classAnimal:11
12 @classmethod13 defclass_method(cls):14 print("class_method_animal")15
16 @staticmethod17 defstatic_method():18 print("static_method_animal")19
20 classCat(Animal):21
22 @classmethod23 defclass_method(cls):24 print("class_method_cat")25
26 @staticmethod27 defstatic_method():28 print("static_method_cat")29
30 c =Cat()31 c.class_method()32 c.static_method()33
34 print(Cat.__dict__)35 print(Animal.__dict__)36
37 Cat.static_method()38 Animal.static_method()39
40
41
42 #以上代码执行结果如下:
43 class_method_cat44 static_method_cat45 {'__module__': '__main__', 'class_method': , 'static_method': , '__doc__': None}46 {'__module__': '__main__', 'class_method': , 'static_method': , '__dict__': , '__weakref__': , '__doc__': None}47 static_method_cat48 static_method_animal
五.继承时使用初始化
1>.手动调用父类的构造方法
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6
7 classA:8 def __init__(self,a,d = 10):9 self.a =a10 self.__d =d11
12 classB:13 def __init__(self,b,c):14 A.__init__(self,b + c,b - c) #我们调用了A的构造方法,那么就可以使用它的属性啦。
15 self.b =b16 self.c =c17
18 defprintv(self):19 print(self.b)20 print(self.a)21
22
23 f = B(200,300)24 print(f.__dict__)25 print(f.__class__.__bases__)26 f.printv()27
28
29
30 #以上代码执行结果如下:
31 {'a': 500, '_A__d': -100, 'b': 200, 'c': 300}32 (,)33 200
34 500
2>.自动调用父类的构造方法
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6 classAnimal:7 def __init__(self,age):8 print("init in Animal")9 self.age =age10
11 defshow(self):12 print(self.age)13
14 classCat(Animal):15 def __init__(self,age,weight):16 #调用父类的__init__方法的顺序有时决定着show方法的结果
17 super().__init__(age)18 print("init in Cat")19 self.age = age + 1
20 self.weight =weight21 #super().__init__(age) #调用父类的方法其实也可以不用放在第一行,在Java语言中必须放在构造方法的首行。
22
23 c = Cat(10,5)24 c.show()25
26
27
28 #以上代码执行结果如下:
29 init inAnimal30 init inCat31 11
3>.属性的继承说明
一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其它类的方法,即使是父类或者派生类的方法。
六.多继承
1>.Python不同版本的类概述
Python2.2之前类时没有共同祖先的,之后,引入object类,它时所有类的共同祖先类object。
Python2.7.X中为了兼容,分为古典类(旧式类)和新式类。
Python3中全部都是新式类。
新式类都是继承自object,新式类可以使用super。
2>.Python多继承实现
在面向对象这种,父类,子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表现,就是多态。一个类继承自多个类就是多继承,它将具有多个类的特征。
多继承毕竟会带来路径选择问题,究竟继承哪个父类的特征呢?如上图所示,左图是多继承(菱形继承),右图是单一继承。
Python使用MRO(method resolution order方法解析顺序)解决基类搜索顺序问题。
历史原因,MRO有三个搜素算法:
经典算法,按照定义从左到右,深度优先策略。2.2版本之前左图的MRO是MyClass->D->B->A->C->A
新式类算法,是经典算法的升级,深度优先,重复的只保留最后一个。2.2版本左图的MRO是MyClass->D->B->C->A->object
C3算法,在类被创建出来的时候,就计算一个MRO有序列表。2.3之后,Python3唯一支持的算法左图中的MRO是MyClass->D->B->C-A->object的列表。C3算法解决多继承的二义性。
经典算法有很大的问题,如果C中有覆盖A的方法,也不会访问到它,因为先访问A的(深度优先)。
新式类算法,依然采用了深度优先,解决重复问题,但是同经典算法一样,没有解决继承的单调性。
C3算法,解决了继承的单调性,它阻止创建之前产生二义性的代码。求得的MRO本质是为了线性化,且确定了顺序。
单调性:假设有A,B,C三个类,C的mro是[C,A,B],那么C的子类的mro中,A,B的顺序一致就是单调的。
3>.多继承的缺点
多继承很好的模拟了世界,因为事物很少是单一继承,但是舍弃简单,必然引入复杂性,带来了冲突。
如同一个孩子继承了来自父母双方的特征。那么到底眼睛像爸爸还是妈妈呢?孩子究竟该像谁多一点呢?
多继承的实现会导致编译器设计的复杂度增加,所以有些高级编程语言舍弃了类的多继承。
C++支持多继承;Java舍弃了多继承。
Java中,一个类可以实现多个接口,一个接口也可以继承多个接口。Java的接口很纯粹,只是方法的声明,继承者必须实现这些方法,就具有了这些能力。就能干什么。
多继承可能会带来二义性,例如,猫和狗都继承自动物类,现在如果一个多继承了猫和狗类,猫和狗都有shout方法,子类究竟继承谁的shout呢?解决方案:实现多继承的语言,要解决二义性,深度优先或者广度优先。
当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。
Python语法时允许多继承,但Python代码时解释执行,只是执行到的时候才发现错误。
团队协作开发,如果引入多继承,那代码很可能不可控。
不管编程语言是否支持多继承,都应当避免多继承。
Python的面向对象,我们看到的太灵活了,太开放了,所以要团队守规矩。
七.Mimin类
1>.单继承存在的弊端案例
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6 classDocument:7 def __init__(self,content):8 self.content =content9
10 def print(self): #基类中只定义,不实现的方法,称为“抽象方法”。在python中,如果采用这种方式定义的抽象方法,子类可以不实现,知道子类使用该方法的时候才报错。
11 """
12 基类提供的方法可以不具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。13 """
14 raiseNotImplementedError()15
16 classWord(Document):17 pass
18
19 classPdf(Document):20 pass
21
22
23 """
24 抛出问题:25 从上面的案例可以看出print算是一种能力(打印功能),不是所有的Document的子类都需要的,所以从这个角度触发,上面的基类Document设计有点问题。26
27 解决思路:28 如果在现有子类Word或Pdf上直接增加,虽然可以,却违反了OCP的原则(多用“继承”,少修改),所以可以继承后增加打印功能。29
30 在这个时候发现,为了增加一种能力,就要增加一次继承,类可能太多了,继承的方式不是很好了。31
32 功能太多,A类需要某几样功能,B类需要另几样功能,他们需要的是多个功能的自由组合,继承实现很繁琐。33
34 我们可以引入类装饰器来解决问题,装饰器的有点在于:35 简单方便,在需要的地方动态增加,直接使用装饰器36 可以为类灵活的增加功能。37
38 但是类装饰器不可继承,因此我们引入Mixin方案,Mixin就是其它类混合进来,同时带来了类的属性和方法。39
40 Mixin类本质上就是多继承实现的。Mixin体现的是一种组合的设计模式。41 """
2>.Mixin案例展示
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:/yinzhengjie
5
6 class Document: #假设该类为第三方库,不允许修改
7 def __init__(self,content):8 self.content =content9
10
11 class Word(Document): #假设该类为第三方库,不允许修改
12 pass
13
14 class Pdf(Document): #假设该类为第三方库,不允许修改
15 pass
16
17
18
19 def printable(cls): #定义类装饰器
20 def_print(self):21 print(self.content,"装饰器")22 cls.print =_print23 returncls24
25 @printable26 class PrintablePdf(Word): #使用类装饰器和Mixin进行使用上的对比
27 pass
28
29 print(PrintablePdf.__dict__)30 print(PrintablePdf.mro())31
32
33 class PrintableMimin: #Mixin就是其它类混合进来,同时带来了类的属性和方法,这里看来和装饰器效果一样,也没有什么特别的。但是Mixin是类,就可以继承。
34 def print(self):35 print(self.content,"Mixin")36
37 class PrintableWord(PrintableMimin,Word): #Mixin类本质上就是多继承实现的。Mixin体现的是一种组合的设计模式。
38 pass
39
40 print(PrintableWord.__dict__)41 print(PrintableWord.mro())42
43
44 pw = PrintableWord("test string")45 pw.print()46
47 classSuperPrintableMixin(PrintableMimin):48 def print(self):49 print("{0} 打印增强前 {0}".format("*" * 20))50 super().print()51 print("{0} 打印增强后 {0}".format("*" * 20))52
53
54 classSuperPrintablePdf(SuperPrintableMixin,Pdf):55 pass
56
57 print(SuperPrintablePdf.__dict__)58 print(SuperPrintablePdf.mro())59
60 spp = SuperPrintablePdf("super print pdf")61 spp.print()62
63
64
65 #以上代码执行结果如下:
66 {'__module__': '__main__', '__doc__': None, 'print': ._print at 0x0000016E8C6459D8>}67 [, , , ]68 {'__module__': '__main__', '__doc__': None}69 [, , , , ]70 test string Mixin71 {'__module__': '__main__', '__doc__': None}72 [, , , , , ]73 ******************** 打印增强前 ********************
74 super printpdf Mixin75 ******************** 打印增强后 ********************
3>.Mixin类的使用原则
在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自抓不同的类提供,这就需要很多的类组合在一起。
从设计模式的角度来说,应该多组合,少继承。
Mixin类的使用原则:
Mixin类中不应该显式的出现__init__初始化方法。
Mixin类通常不能独立工作,因为它式准备混入别的类中的部分功能实现。
Mixin类的祖先类也应该式Mixin类。
使用时,Mixin类通常在继承列表的第一个位置,例如上来中的"class PrintableWord(PrintableMixin,Word):pass"
Mixin类和装饰器:
这两种方式都可以使用,看个人喜好。
如果还需要继承就得使用Mixin类的方式。