1、引子
def person(name,age,sex,job): data = { 'name':name, 'age':age, 'sex':sex, 'job':job } return data def dog(name,dog_type): data = { 'name':name, 'type':dog_type } return data
上面两个方法相当于造了两个模子,游戏开始,你得生成一个人和狗的实际对象吧,怎么生成呢?
d1 = dog("李磊","京巴") p1 = person("严帅",36,"F","运维") p2 = person("林海峰",27,"F","Teacher")
两个角色对象生成了,狗和人还有不同的功能呀,狗会咬人,人会打狗,对不对? 怎么实现呢?想到了, 可以每个功能再写一个函数,想执行哪个功能,直接 调用 就可以了,对不?
def bark(d): print("dog %s:wang.wang..wang..."%d['name']) def walk(p): print("person %s is walking..." %p['name'])
d1 = dog("李磊","京巴")p1 = person("严帅",36,"F","运维")p2 = person("林海峰",27,"F","Teacher")walk(p1) bark(d1)
但是仔细玩耍一会,你就不小心干了下面这件事:
p1 = person("严帅",36,"F","运维")bark(p1) # 把人的对象传给了狗的方法, 人家不干了
事实 上,这并没出错。很显然,人是不能调用狗的功能的,如何在代码级别实现这个限制呢?
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 def person(name, age, sex, job): 6 def walk(p): 7 print("person %s is walking..." % p['name']) 8 data = { 9 'name': name,10 'age': age,11 'sex': sex,12 'job': job,13 'walk': walk14 }15 return data16 17 18 def dog(name, dog_type):19 def bark(d):20 print("dog %s:wang.wang..wang..." % d['name'])21 data = {22 'name': name,23 'type': dog_type,24 'bark': bark25 }26 return data27 28 d1 = dog("李磊","京巴")29 p1 = person("严帅",36,"F","运维")30 p2 = person("林海峰",27,"F","Teacher")31 32 d1['bark'](p1) # 把人这个对象传给了狗
你是如此的机智,这样就实现了限制人只能用人自己的功能啦。
但,我的哥,不要高兴太早,刚才你只是阻止了两个完全 不同的角色 之前的功能混用, 但有没有可能 ,同一个种角色,但有些属性是不同的呢? 比如 ,大家都打过cs吧,cs里有警察和恐怖份子,但因为都 是人, 所以你写一个角色叫 person(), 警察和恐怖份子都 可以 互相射击,但警察不可以杀人质,恐怖分子可以,这怎么实现呢? 你想了说想,说,简单,只需要在杀人质的功能里加个判断,如果是警察,就不让杀不就ok了么。 没错, 这虽然 解决了杀人质的问题,但其实你会发现,警察和恐怖分子的区别还有很多,同时又有很多共性,如果 在每个区别处都 单独做判断,那得累死。
你想了想说, 那就直接写2个角色吧, 反正 这么多区别, 我的哥, 不能写两个角色呀,因为他们还有很多共性 , 写两个不同的角色,就代表 相同的功能 也要重写了,是不是我的哥? 。。。
好了, 话题就给你点到这, 再多说你的智商 也理解不了了!
2、面向对象 v.s. 面向过程
编程范式
编程是 程序 员 用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程 , 一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。 不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路, 大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。 两种最重要的编程范式分别是面向过程编程和面向对象编程。
面向过程编程(Procedural Programming)
Procedural programming uses a list of instructions to tell the computer what to do step-by-step. 面向过程编程依赖 - 你猜到了- procedures,一个procedure包含一组要被进行计算的步骤, 面向过程又被称为top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。举个典型的面向过程的例子, 数据库备份, 分三步,连接数据库,备份数据库,测试备份文件可用性。
代码如下:
def db_conn(): print("connecting db...") def db_backup(dbname): print("导出数据库...",dbname) print("将备份文件打包,移至相应目录...") def db_backup_test(): print("将备份文件导入测试库,看导入是否成功") def main(): db_conn() db_backup('my_db') db_backup_test() if __name__ == '__main__': main()
这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改, 举个例子,如果程序开头你设置了一个变量值为1 , 但如果其它子过程依赖这个值为1的变量才能正常运行,那如果你改了这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程,那就会发生一连串的影响,随着程序越来越大, 这种编程方式的维护难度会越来越高。
所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。面向对象编程
OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
面向对象的几个核心特性如下
Class 类
一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法。Object 对象
一个对象即是一个类的实例化的结果,一个类必须经过实例化后方可在程序中调用,一个类可以多次实例化出多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同。Encapsulation 封装
在类中数据的赋值或内部调用等操作对外部用户来说是透明的,这使类变成了一个胶囊或容器,里面包含着类的属性和方法。Inheritance 继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承。Polymorphism 多态
多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,即同一事物表现出多种形态。编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定。
3、面向对象编程(Object-Oriented Programming )介绍
类的语法
class Dog(object): print("hello,I am a dog!") d = Dog() # 实例化这个类,此时的d就是类Dog的一个实例化对象 # 实例化,其实就是以Dog类为模版,在内存里开辟一块空间,存上数据,赋值成一个变量名
上面的代码其实有问题,想给狗起名字传不进去。
class Dog(object): # 新式类 def __init__(self,name,dog_type): self.name = name self.type = dog_type def sayhi(self): print("hello,I am a dog, my name is ",self.name) d = Dog('LiChuang',"京巴")d.sayhi()
类的实例化示意图:
根据上图我们得知,其实self,就是实例本身!你实例化时python会自动把这个实例本身通过self参数传进去。
首先来看一下以下代码,理解一些概念(新式类/经典类,类变量/实例变量,私有属性/私有方法,构造方法/析构方法):
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 # class Role(object): 新式类 6 class Role: # 经典类 7 n = 123 8 n_list = [] 9 Name = '我是类name'10 name = 'Class name'11 '''12 以上变量n, n_list, Name, name均为类变量13 '''14 15 def __init__(self, name, role, weapon, life_value=100,money=15000):16 '''__init__()为构造函数/方法,用于初次调用时的变量等的初始化'''17 self.name = name18 self.role = role # 实例变量19 self.weapon = weapon20 21 '''私有属性——只有在类Role中才能访问,外部无法访问'''22 self.__life_value = life_value # 私有属性23 self.money = money24 25 def __del__(self):26 '''27 析构函数/方法,在程序运行完毕时或引用此类实例化的对象的引用次数为0时自动执行的操作28 其实,写明析构函数只是对系统默认的析构方法的重载而已29 '''30 print('%s 彻底死了...' %self.name)31 32 def show_status(self):33 print('name:%s weapon:%s life_value:%s' %(self.name,34 self.weapon,35 self.__life_value))36 37 def __shot(self): # 私有方法38 '''私有方法——只有在类Role中才能访问,外部无法访问'''39 print('shooting...')40 41 def got_shot(self):42 self.__life_value -= 50 # 对私有属性的修改43 print('%s:ah.....,I got a shot...' %self.name)44 45 def buy_gun(self, gun_name):46 print('%s just bought %s' %(self.name, gun_name))47 48 r1 = Role('Tim', 'police', 'AK47')49 r1.buy_gun('B22')50 r1.got_shot()51 # del r1 # 手动删除变量(取消引用关系,使得对象r1的引用次数为0)52 # 此时程序尚未结束,会调用一次析构函数53 54 r2 = Role('Tom', 'police', 'AK47')55 r2.got_shot()56 r2.show_status()57 '''当没有主动删除引用关系时其结果是:(del r1)58 Tim just bought B2259 Tim:ah.....,I got a shot...60 Tom:ah.....,I got a shot...61 name:Tom weapon:AK47 life_value:5062 Tim 彻底死了...63 Tom 彻底死了...64 '''65 66 '''私有方法和私有属性外部无法访问'''67 # print(r2.__life_value)68 # AttributeError: 'Role' object has no attribute 'life_value'69 # r2.__shot()70 # AttributeError: 'Role' object has no attribute '__shot'
说明并解释以上代码:
1、为什么需要__init__()构造方法?其作用是什么?为什么多个函数内部的形参的第一个参数默认是self,其作用是什么?
其解答参考:
2、经典类与新式类的比较:
参考:
3、类变量和实例变量:
参考:
4、私有属性和私有方法,析构函数
4、面向对象的特性
4.1 封装
封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
4.2 继承
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
继承概念的实现方式主要有2类:实现继承、接口继承。
- 实现继承是指使用基类的属性和方法而无需额外编码的能力;
- 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构父类方法);
OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。
单继承:即子类继承(唯一的)父类的属性和方法
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 class People: 6 def __init__(self, name, age): 7 self.name = name 8 self.age = age 9 10 def eat(self):11 print('%s is eatting...' %self.name)12 13 def talk(self):14 print('%s is talking...' %self.name)15 16 def sleep(self):17 print('%s is sleeping...' %self.name)18 19 20 class Man(People):21 def hell(self): # 对父类的扩展22 print('%s is going to hell...' %self.name)23 24 def sleep(self): # 重构父类25 # People.sleep(self)26 super(Man, self).sleep() # 先调用父类27 print('man is sleeping')28 29 class Woman(People):30 def get_birth(self):31 print('%s is building a baby...' %self.name)32 33 m1 = Man('Tim', 22)34 m1.eat()35 m1.hell()36 m1.sleep()37 38 w1 = Woman('Kancy', 28)39 w1.get_birth()40 w1.eat()41 w1.sleep()42 43 '''44 Tim is eatting...45 Tim is going to hell...46 Tim is sleeping...47 man is sleeping48 Kancy is building a baby...49 Kancy is eatting...50 Kancy is sleeping...51 '''
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 # class People: 经典类 6 class People(object): # 新式类 7 def __init__(self, name, age): 8 self.name = name 9 self.age = age10 11 def eat(self):12 print('%s is eatting...' %self.name)13 14 def talk(self):15 print('%s is talking...' %self.name)16 17 def sleep(self):18 print('%s is sleeping...' %self.name)19 20 21 class Man(People):22 def __init__(self, name, age, position):23 '''24 重构构造方法,即子类的构造函数覆盖掉父类的,25 当实例化一个Man类型的对象时,执行子类Man的构造函数26 而不执行父类中的构造函数27 '''28 # super().__init__(name, age)29 # People.__init__(self, name, age) # 经典类写法30 super(Man,self).__init__(name, age) # 新式类写法(这里重用了父类的构造方法)31 self.position = position32 print('%s has a(n) %s' %(self.name, self.position))33 34 def hell(self):35 print('%s is going to hell...' %self.name)36 37 def sleep(self): # 重构父类38 People.sleep(self)39 print('man is sleeping')40 41 class Relationship(object):42 def make_friends(self, obj):43 print('%s makes friends with %s' %(self.name, self.obj))44 45 class Woman(People):46 def get_birth(self):47 print('%s is building a baby...' %self.name)48 49 m1 = Man('Tim', 30, position='elite')50 m1.eat()51 m1.hell()52 m1.sleep()53 54 w1 = Woman('Kancy', 28)55 w1.get_birth()56 57 '''58 Tim has a(n) elite59 Tim is eatting...60 Tim is going to hell...61 Tim is sleeping...62 man is sleeping63 Kancy is building a baby...64 '''65 66 # m1 = Man('Tim', 30) 当实例化时,传入2个参数会抛异常67 # 因为此时实例化执行的Man中的构造函数68 '''69 Traceback (most recent call last):70 File "E:/python14_workspace/s14/day06/inherit_1.py", line 50, in71 m1 = Man('Tim', 30)72 TypeError: __init__() missing 1 required positional argument: 'position'73 '''
多(重)继承:是指python的类可以有两个以上父类,也即有类A,类B,类C,C同时继承类A与类B,此时C中可以使用A与B中的属性与方法
那么问题来了,如果A与B中具有相同名字的方法,这个时候python怎么调用的会是哪个方法呢?
代码段1:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 class A(object): 6 def __init__(self): 7 pass 8 def foo(self): 9 print('A foo...')10 11 class B(object):12 def __init__(self):13 pass14 def foo(self):15 print('B foo...')16 17 class C(A, B):18 def __init__(self):19 pass20 21 testc = C()22 testc.foo() # A foo...
实际上打印出来的信息是 A foo,这就说明了调用的是A中的方法。其实在python2.2之后,多继承中基类的寻找顺序是一种广度优先算法,称之为。而python2.2之前,使用的是深度优先算法来寻找基类方法。在类C的继承关系中,按照广度优先算法,则会先找到靠近C的基类A,在A中找到foo方法之后,就直接返回了,因此即使后面的基类B中也有foo方法,但是这里不会引用它。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 class A(object): 6 def foo(self): 7 print('A foo...') 8 9 class B(object):10 def foo(self):11 print('B foo...')12 def bar(self):13 print('B bar...')14 15 class C1(A, B):16 pass17 18 class C2(A, B):19 def bar(self):20 print('C2-bar...')21 22 class D(C1, C2):23 pass24 25 if __name__ == '__main__':26 print(D.__mro__)27 d = D()28 d.foo()29 d.bar()30 31 # (,32 # ,33 # ,34 # ,35 # ,36 # )37 # A foo...38 # C2-bar...
执行的结果为:
(<class '__main__.D'>, <class '__main__.C1'>, <class '__main__.C2'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
A foo (实际上搜索顺序为D=>C1=>A)
C2 bar(实际上搜索顺序为D=>C1=>C2)可以看到,foo找到的是A类中的方法,bar找到的是C2中的方法。
其实新式类的搜索方法是采用了“广度优先”的方式去查找属性。
只有新式类有属性,该属性标记了python继承层次中父类查找的顺序,python多重继承机制中就是按照__mro__的顺序进行查找,一旦找到对应属性,则查找马上返回。
经过上面的__mro__输出可以发现,D类的继承查找路径为:D=>C1=>C2=>A=>B=>object,通过该查找路径,foo方法将会调用A的foo方法,、bar方法将调用C2的方法,通过实际实验调用,查看输出内容确实与__mro__顺序一样。
参考了:
多继承之super()
一、问题的发现与提出
在Python类的方法(method)中,要调用父类的某个方法,在Python 2.2以前,通常的写法如代码段1:
代码段1:
#!/usr/bin/env python# -*- coding:utf-8 -*-# Author: antcoloniesclass A(object): def __init__(self): print('Enter A') print('Leave A')class B(A): def __init__(self): print('Enter B') A.__init__(self) print('Leave B')b = B()'''Enter BEnter ALeave ALeave B'''
即,使用非绑定的类方法(用类名来引用的方法),并在参数列表中,引入待绑定的对象(self),从而达到调用父类的目的。
这样做的缺点是,当一个子类的父类发生变化时(如类B的父类由A变为C时),必须遍历整个类定义,把所有的通过非绑定的方法的类名全部替换过来,例如代码段2:class B(C): # A --> C def __init__(self): print "enter B" C.__init__(self) # A --> C print "leave B"
如果代码简单,这样的改动或许还可以接受。但如果代码量庞大,这样的修改可能是灾难性的。
因此,自Python 2.2开始,Python添加了一个关键字super,来解决这个问题。下面是Python 2.3的官方文档说明:super(type[, object-or-type])Return the superclass of type. If the second argument is omitted the super object, returned is unbound. If the second argument is an object, isinstance(obj, type) must be true. If the second argument is a type, issubclass(type2, type) must be true. super() only works for new-style classes.A typical use for calling a cooperative superclass method is: class C(B): def meth(self, arg): super(C, self).meth(arg)New in version 2.2.从说明来看,可以把类B改写如代码段3:class A(object): # A must be new-style class def __init__(self): print "enter A" print "leave A"class B(C): # A --> C def __init__(self): print "enter B" super(B, self).__init__() print "leave B"
尝试执行上面同样的代码,结果一致,但修改的代码只有一处,把代码的维护量降到最低,是一个不错的用法。因此在我们的开发过程中,super关键字被大量使用,而且一直表现良好。
在我们的印象中,对于super(B, self).__init__()是这样理解的:super(B, self)首先找到B的父类(就是类A),然后把类B的对象self转换为类A的对象(通过某种方式,一直没有考究是什么方式,惭愧),然后“被转换”的类A对象调用自己的__init__函数。考虑到super中只有指明子类的机制,因此,在多继承的类定义中,通常我们保留使用类似代码段1的方法。有一天某同事设计了一个相对复杂的类体系结构(我们先不要管这个类体系设计得是否合理,仅把这个例子作为一个题目来研究就好),代码如代码段4:1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 class A(object): 6 def __init__(self): 7 print('Enter A') 8 print('Leave A') 9 10 class B(object):11 def __init__(self):12 print('Enter B')13 print('Leave B')14 15 class C(A):16 def __init__(self):17 print('Enter C')18 super(C,self).__init__()19 print('Leave C')20 21 class D(A):22 def __init__(self):23 print('Enter D')24 super(D,self).__init__()25 print('Leave D')26 27 class E(B,C):28 def __init__(self):29 print('Enter E')30 B.__init__(self)31 C.__init__(self)32 print('Leave E')33 34 class F(E,D):35 def __init__(self):36 print('Enter F')37 E.__init__(self)38 D.__init__(self)39 print('Leave F')40 41 if __name__ == '__main__':42 print(F.__mro__)43 f = F()44 45 '''46 (,47 ,48 ,49 ,50 ,51 ,52 )53 Enter F54 Enter E55 Enter B56 Leave B57 Enter C58 Enter D59 Enter A60 Leave A61 Leave D62 Leave C63 Leave E64 Enter D65 Enter A66 Leave A67 Leave D68 Leave F69 '''70 '''71 object72 | \73 B A74 | / |75 | C D76 \ / |77 E |78 \ |79 F80 '''
明显地,类A和类D的初始化函数被重复调用了2次,这并不是我们所期望的结果!我们所期望的结果是最多只有类A的初始化函数被调用2次——其实这是多继承的类体系必须面对的问题。我们把代码段4的类体系画出来,如下图:
object| \B A| / || C D\ / | E | \ | F按我们对super的理解,从图中可以看出,在调用类C的初始化函数时,应该是调用类A的初始化函数,但事实上却调用了类D的初始化函数。好一个诡异的问题!也就是说,mro中记录了一个类的所有基类的类类型序列。查看mro的记录,发觉包含7个元素,7个类名分别为: F E B C D A object从而说明了为什么在C.__init__中使用super(C, self).__init__()会调用类D的初始化函数了。 ???我们把代码段4改写为:1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 class A(object): 6 def __init__(self): 7 print('Enter A') 8 super(A,self).__init__() # New 9 print('Leave A')10 11 class B(object):12 def __init__(self):13 print('Enter B')14 super(B, self).__init__() # New15 print('Leave B')16 17 class C(A):18 def __init__(self):19 print('Enter C')20 super(C,self).__init__()21 print('Leave C')22 23 class D(A):24 def __init__(self):25 print('Enter D')26 super(D,self).__init__()27 print('Leave D')28 29 class E(B,C):30 def __init__(self):31 print('Enter E')32 super(E, self).__init__() # change33 print('Leave E')34 35 class F(E,D):36 def __init__(self):37 print('Enter F')38 super(E, self).__init__() # change39 print('Leave F')40 41 if __name__ == '__main__':42 print(F.__mro__)43 f = F()44 45 '''46 (,47 ,48 ,49 ,50 ,51 ,52 )53 Enter F54 Enter E55 Enter B56 Enter C57 Enter D58 Enter A59 Leave A60 Leave D61 Leave C62 Leave B63 Leave E64 Leave F65 '''66 '''67 object68 | \69 B A70 | / |71 | C D72 \ / |73 E |74 \ |75 F76 '''
明显地,F的初始化不仅完成了所有的父类的调用,而且保证了每一个父类的初始化函数只调用一次。
再看类结构:object / \ / A | / \ B-1 C-2 D-2 \ / / E-1 / \ / F
E-1,D-2是F的父类,其中表示E类在前,即F(E,D)。
所以初始化顺序可以从类结构图来看出 : F->E->B -->C --> D --> A由于C,D有同一个父类,因此会先初始化D再是A。二、延续的讨论我们再重新看上面的类体系图,如果把每一个类看作图的一个节点,每一个从子类到父类的直接继承关系看作一条有向边,那么该体系图将变为一个有向图。不难发现mro的顺序正好是该有向图的一个拓扑排序序列。从而,我们得到了另一个结果——Python是如何去处理多继承。支持多继承的传统的面向对象程序语言(如C++)是通过虚拟继承的方式去实现多继承中父类的构造函数被多次调用的问题,而Python则通过mro的方式去处理。但这给我们一个难题:对于提供类体系的编写者来说,他不知道使用者会怎么使用他的类体系,也就是说,不正确的后续类,可能会导致原有类体系的错误,而且这样的错误非常隐蔽的,也难于发现。三、小结 1. super并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,产生了一个super对象; 2. super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例; 3. super(B, self).func的调用并不是用于调用当前类的父类的func函数; 4. Python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数只调用一次(如果每个类都使用super); 5. 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一个父类函数被调用多次。参考了:
多重继承实例:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 class School(object): 6 def __init__(self, name, addr): 7 self.name = name 8 self.addr = addr 9 self.students = [] 10 self.teachers = [] 11 self.staffs = [] 12 def enroll(self, stu_obj): 13 print('enrolling for students...') 14 self.students.append(stu_obj) 15 def hire(self, staff_obj): 16 print('hiring new staffs...') 17 self.staffs.append(staff_obj) 18 19 20 class SchoolMember(object): 21 def __init__(self, name, age, gender): 22 self.name = name 23 self.age = age 24 self.gender = gender 25 26 def tell(self): 27 # print() 28 pass 29 30 class Teacher(SchoolMember): 31 def __init__(self, name, age, gender, salary, course): 32 super(Teacher, self).__init__(name, age, gender) 33 self.salary = salary 34 self.course = course 35 36 def tell(self): 37 print(''' 38 ----- info of Teacher:%s ---- 39 Name: %s 40 Gender: %s 41 Salary: %s 42 Course: %s 43 ''' %(self.name, self.name, self.gender, self.salary, self.course)) 44 45 def teach(self): 46 print('%s is teaching course [%s]' %(self.name, self.course)) 47 48 49 class Student(SchoolMember): 50 def __init__(self, name, age, gender, std_id, grade): 51 super(Student, self).__init__(name, age, gender) 52 self.std_id = std_id 53 self.grade = grade 54 def tell(self): 55 print(''' 56 ----- info of Student:%s ----- 57 Name: %s 58 Gender: %s 59 Std_is: %s 60 Grade: %s 61 ''' %(self.name, self.name, self.gender, self.std_id, self.grade)) 62 def pay_tuition(self, amount): 63 print('%s has paid tuition for $%s' %(self.name, amount)) 64 65 66 school = School('oldboyIT', 'sandriver') 67 68 t1 = Teacher('Oldboy', 56, 'MF', 2000000, 'Linux') 69 t2 = Teacher('Alex', 22, 'MF', 3000000, 'PythonDevOps') 70 71 s1 = Student('Tim', 36, 'MF', 1001, 'PythonDevOps') 72 s2 = Student('Bruce', 19, 'MF', 1002, 'Linux') 73 74 t1.tell() 75 s1.tell() 76 77 school.enroll(s1) 78 school.enroll(s2) 79 school.hire(t1) 80 81 print(school.students) 82 print(school.staffs) 83 school.staffs[0].teach() 84 85 for stu in school.students: 86 stu.pay_tuition(5000) 87 88 ''' 89 ----- info of Teacher:Oldboy ---- 90 Name: Oldboy 91 Gender: MF 92 Salary: 2000000 93 Course: Linux 94 95 96 ----- info of Student:Tim ----- 97 Name: Tim 98 Gender: MF 99 Std_is: 1001100 Grade: PythonDevOps101 102 enrolling for students...103 enrolling for students...104 hiring new staffs...105 [<__main__.Student object at 0x0000000001F448D0>, <__main__.Student object at 0x0000000001F44908>]106 [<__main__.Teacher object at 0x0000000001F44860>]107 Oldboy is teaching course [Linux]108 Tim has paid tuition for $5000109 Bruce has paid tuition for $5000110 '''
4.3 多态
多态(Polymorphism),是指面向对象程序运行时,相同的消息可能会送给多个不同的类之对象,而系统可依据对象所属类,引发对应类的方法,而有不同的行为。
简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作称之。
在面向对象程序设计中,多态一般指子类型多态(Subtype polymorphism)。
多态实例1(比如不同类型的动物的叫声):
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 class Animal(object): 6 def __init__(self, name): 7 self.name = name 8 def talk(self): 9 raise NotImplementedError('Subclass must implement abstract method')10 11 class Cat(Animal):12 def talk(self):13 return 'Meow...'14 15 class Dog(Animal):16 def talk(self):17 return 'Woof...'18 19 animals = [Cat('Missy'),20 Cat('Jeff'),21 Dog('Lassie')]22 23 for animal in animals:24 print('%s : %s' %(animal.name, animal.talk()))25 26 '''27 Missy : Meow...28 Jeff : Meow...29 Lassie : Woof...30 '''
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author: antcolonies 4 5 class Animal(object): 6 def __init__(self, name): 7 self.name = name 8 def talk(self): 9 raise NotImplementedError('Subclass must implement abstract method')10 11 @staticmethod # 静态方法12 def animal_talk(obj):13 return obj.talk()14 15 class Cat(Animal):16 def talk(self):17 return 'Meow...'18 19 class Dog(Animal):20 def talk(self):21 return 'Woof...'22 23 c = Cat('Missy')24 c1 = Cat('Jeff')25 d = Dog('Lassie')26 27 print('%s : %s' %(c.name, Animal.animal_talk(c)))28 print('%s : %s' %(c1.name, Animal.animal_talk(c1)))29 print('%s : %s' %(d.name, Animal.animal_talk(d)))30 31 '''32 Missy : Meow...33 Jeff : Meow...34 Lassie : Woof...35 '''
多态实例2:一个简单的日志记录函数,用判断实现的,重构为面向对象多态来实现。(
如果有大量的判断语句,就可以用多态来实现
)
1 # 工程应用 2 # 一个简单的日志记录函数,用判断实现的,重构为面向对象多态来实现。 3 #如果有大量的判断语句,就可以用多态来实现。 4 5 6 def log_msg(log_type): 7 msg = 'Operation successful' 8 if log_type == 'file': 9 log_file.write(msg)10 elif log_type == 'database':11 cursor.execute('INSERT INTO log_table (MSG) VALUES ('?')', msg)12 13 #重构14 15 16 class FileLogger(object):17 18 def log(self, msg):19 log_file.write(msg)20 21 22 class DbLogger(object):23 24 def log(self, msg):25 cursor.execute('INSERT INTO log_table (MSG) VALUES ('?')', msg)26 27 28 def log_msg(obj):29 msg = 'Operation successful'30 obj.log(msg)
参考了: