python 面向对象编程(二)
目录:
- 对象、类型与类总结
- 对象之间的交互
- 继承与派生
- 多态与多态性
- 封装
- 绑定与非绑定
- 面向对象的软件开发
- python中关于OOP的常用术语
一、对象、类型与类总结
1、工厂函数(id, is, ==)
id 身份(在pyhton中为python内存指向地址,与type,value组成对象的三个重要元素)
is、 == 身份运算
1 # x=1 #x=int(1) 2 # #id 3 # #type 4 # #value 5 # print(id(x)) #身份 6 # print(type(x)) #类型 7 # print(x) #值 8 # 9 # #is:身份运算 10 # x=1 11 # y=1 12 # print(x is y) 13 ''' 14 结果: 15 true 16 ''' 17 #== #判断的值 18 # print(x == y) 19 ''' 20 结果: 21 true 22 '''
2、类的用法:实例化、属性引用(如:增删改查)
1 ''' 2 2、类的用法 3 ''' 4 class Chinese: 5 country = 'China' 6 def __init__(self,name,age): 7 # print('====>') 8 self.name = name #p1.name='egon' 9 self.age = age #p1.age=18 10 11 def talk(self): 12 print('say chinese',self) 13 14 15 #类的第一种用法:实例化 16 p1=Chinese('egon', 18) #__init__(p1,'egon',18) 17 18 19 #类的第二种用法:属性引用(增删改查) 20 print(Chinese.country) #类的数据属性 21 print(Chinese.__init__) #类的函数属性 22 print(Chinese.__dict__) #查看类的属性字典,或者说名称空间 23 ''' 24 结果: 25 China 26 <function Chinese.__init__ at 0x000000FB722EC8C8> 27 {'__module__': '__main__', 'country': 'China', '__init__': <function Chinese.__init__ at 0x000000E1EBB5C8C8>, 'talk': <function Chinese.talk at 0x000000E1EBB5C950>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None} 28 ''' 29 30 # print(Chinese.country) #等于 31 # print(Chinese.__dict__['country']) 32 33 ''' 34 结果: 35 China 36 China 37 '''
ps. __dict__ 新式类构建查看(名称空间)
__init__ 新式类构建方法
3、对象:
1 ''' 2 3、对象 3 ''' 4 class Chinese: 5 country = 'China' 6 def __init__(self,name,age): 7 # print('====>') 8 self.name=name #p1.name='egon' 9 self.age=age #p1.age=18 10 11 def talk(self): 12 print('say chinese',self) 13 14 # 对象 15 p1=Chinese('shuyang',18) #__init__(p1,'egon',18) 16 17 print(p1.name) 18 print(p1.age) 19 20 print(p1.__dict__) 21 print(p1.__dict__['name']) 22 ''' 23 结果: 24 egon 25 18 26 {'name': 'egon', 'age': 18} 27 egon 28 '''
4、类型与类:
a.类型与类是统一的
b.定义在类内部的变量,是所有对象共有的,id全一样
c.定义在类内部的函数,是绑定到所有对象的,是给对象来用,obj.func() 会把obj本身当做第一个参数出入
d.绑定方法:绑定到谁身上,就是给谁用的,谁来调用就会自动把自己当做第一个参数传入
e.print(p1.x) #先从p1.__dict__,找不到再找Chinese.__dict__,找不到就会报错
1 ''' 2 4、类型与类: 3 ''' 4 class Chinese: 5 country = 'China' 6 def __init__(self,name,age): 7 # print('====>') 8 self.name=name #p1.name='egon' 9 self.age=age #p1.age=18 10 11 def talk(self): 12 print('say chinese',self) 13 14 p1=Chinese('shuyang',18) #__init__(p1,'egon',18) 15 p2=Chinese('111',1000) #__init__(p1,'egon',18) 16 print(id(p1.country)) 17 print(id(p2.country)) 18 print(id(Chinese.country)) 19 20 print(Chinese.talk) 21 print(p1.talk) 22 print(p2.talk) 23 24 print(p2) 25 p2.talk() 26 Chinese.talk(p1) 27 ''' 28 结果: 29 848811430328 30 848811430328 31 848811430328 32 <function Chinese.talk at 0x000000C5A11AC950> 33 <bound method Chinese.talk of <__main__.Chinese object at 0x000000C5A11A9940>> 34 <bound method Chinese.talk of <__main__.Chinese object at 0x000000C5A11A99B0>> 35 <__main__.Chinese object at 0x000000C5A11A99B0> 36 say chinese <__main__.Chinese object at 0x000000C5A11A99B0> 37 say chinese <__main__.Chinese object at 0x000000C5A11A9940> 38 '''
其他类与对象详细解释:http://www.cnblogs.com/wangshuyang/p/6943645.html
二、对象之间的交互
1、类的统计
1 class count_obj: 2 count_num = 0 3 def __init__(self, Name, Age): 4 # print(self.count) 5 # count+=1 #全局的count 6 # self.count=10 # 对象自己 7 self.name = Name 8 self.age = Age 9 10 count_obj.count_num += 1 11 12 p1 = count_obj('wang', '18') # Foo.count+=1 Foo.count=1 13 print(count_obj.count_num) # 等于p1.count_num 14 print(p1.count_num) 15 p2 = count_obj('shu', '18') # Foo.count+=1 Foo.count=1 16 print(count_obj.count_num) 17 print(p1.count_num) 18 print(p2.count_num) 19 ''' 20 结果: 21 1 22 1 23 2 24 2 25 2 26 '''
2、定义学生类
1 # class Student: 2 # tag=123123123123123 3 # def __init__(self,ID,name,age): 4 # self.id=ID 5 # self.name=name 6 # self.age=age 7 # def walk(self): 8 # print('%s is walking' %self.name) 9 # 10 # s1=Student(1,'egon',18) 11 # s2=Student(2,'alex',10000) 12 # 13 # print(s1.id) 14 # print(s1.name) 15 # print(s1.age) 16 # print(s1.tag) 17 # print(s2.tag) 18 # 19 # 20 # s1.walk() 21 # s2.walk()
3、对象之间的交互
1 ''' 2 对象之间的交互 3 ''' 4 class Garen: 5 camp='Demacia' 6 def __init__(self,nickname,life_value=200,aggressivity=100): 7 self.nickname=nickname 8 self.life_value=life_value 9 self.aggressivity=aggressivity 10 11 def attack(self,enemy): 12 enemy.life_value-=self.aggressivity # enemy 为另一个对象 13 14 15 class Riven: 16 camp = 'Noxus' 17 18 def __init__(self, nickname, life_value=100, aggressivity=200): 19 self.nickname = nickname 20 self.life_value = life_value 21 self.aggressivity = aggressivity 22 23 def attack(self, enemy): 24 enemy.life_value -= self.aggressivity # enemy 为另一个对象 25 26 27 28 g=Garen('欧德博爱') 29 r=Riven('矮里渴死') 30 print(r.life_value) 31 32 g.attack(r) #发送了一条消息,称为向g发送了attack指令 33 print(r.life_value) 34 ''' 35 结果: 36 100 37 0 38 '''
三、继承与派生
1、什么是继承?
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类。python中类的继承分为:单继承和多继承.
1 class ParentClass1: #定义父类 2 pass 3 4 class ParentClass2: #定义父类 5 pass 6 7 class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass 8 pass 9 10 class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 11 pass
查看继承
1 查看继承 2 >>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 3 (<class '__main__.ParentClass1'>,) 4 >>> SubClass2.__bases__ 5 (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>) 6 提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。 7 >>> ParentClass1.__bases__ 8 (<class 'object'>,) 9 >>> ParentClass2.__bases__ 10 (<class 'object'>,)
ps.python2与python3类的分类区别
1 #python2中类分为:新式类与经典类 2 # class Foo(object): #新式类 3 # pass 4 # 5 # class Bar: #经典类 6 # pass 7 8 #python3中类全都是新式类 9 # class Foo(object): #新式类 10 # pass 11 # class Foo: #新式类 12 # pass 13 # 14 # print(Foo.__bases__)
2、继承与抽象(先抽象再继承)
a.继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
b.抽象即抽取类似或者说比较像的部分。可以分为2个部分:将奥巴马和梅西这俩对象比较像的部分抽取成类、将人,猪,狗这三个类比较像的部分抽取成父类。
c.抽象最主要的作用是划分类别,这样可以隔离关注点,降低复杂度。
d.抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
3、继承与重用性(继承的优点)
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了类的继承的概念。通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。而在子类定义新的属性,覆盖掉父类的属性,称为派生,如:
1 ==========================第一部分 2 例如 3 4 猫可以:喵喵叫、吃、喝、拉、撒 5 6 狗可以:汪汪叫、吃、喝、拉、撒 7 8 如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下: 9 10 11 #猫和狗有大量相同的内容 12 class 猫: 13 14 def 喵喵叫(self): 15 print '喵喵叫' 16 17 def 吃(self): 18 # do something 19 20 def 喝(self): 21 # do something 22 23 def 拉(self): 24 # do something 25 26 def 撒(self): 27 # do something 28 29 class 狗: 30 31 def 汪汪叫(self): 32 print '喵喵叫' 33 34 def 吃(self): 35 # do something 36 37 def 喝(self): 38 # do something 39 40 def 拉(self): 41 # do something 42 43 def 撒(self): 44 # do something 45 46 47 48 ==========================第二部分 49 上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现: 50 51 动物:吃、喝、拉、撒 52 53 猫:喵喵叫(猫继承动物的功能) 54 55 狗:汪汪叫(狗继承动物的功能) 56 57 伪代码如下: 58 class 动物: 59 60 def 吃(self): 61 # do something 62 63 def 喝(self): 64 # do something 65 66 def 拉(self): 67 # do something 68 69 def 撒(self): 70 # do something 71 72 # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 73 class 猫(动物): 74 75 def 喵喵叫(self): 76 print '喵喵叫' 77 78 # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 79 class 狗(动物): 80 81 def 汪汪叫(self): 82 print '喵喵叫' 83 84 85 86 87 ==========================第三部分 88 #继承的代码实现 89 class Animal: 90 91 def eat(self): 92 print("%s 吃 " %self.name) 93 94 def drink(self): 95 print ("%s 喝 " %self.name) 96 97 def shit(self): 98 print ("%s 拉 " %self.name) 99 100 def pee(self): 101 print ("%s 撒 " %self.name) 102 103 104 class Cat(Animal): 105 106 def __init__(self, name): 107 self.name = name 108 self.breed = '猫' 109 110 def cry(self): 111 print('喵喵叫') 112 113 class Dog(Animal): 114 115 def __init__(self, name): 116 self.name = name 117 self.breed='狗' 118 119 def cry(self): 120 print('汪汪叫') 121 122 123 # ######### 执行 ######### 124 125 c1 = Cat('小白家的小黑猫') 126 c1.eat() 127 128 c2 = Cat('小黑的小白猫') 129 c2.drink() 130 131 d1 = Dog('胖子家的小瘦狗') 132 d1.eat()
1 class Hero: 2 def __init__(self,nickname,aggressivity,life_value): 3 self.nickname=nickname 4 self.aggressivity=aggressivity 5 self.life_value=life_value 6 7 def move_forward(self): 8 print('%s move forward' %self.nickname) 9 10 def move_backward(self): 11 print('%s move backward' %self.nickname) 12 13 def move_left(self): 14 print('%s move forward' %self.nickname) 15 16 def move_right(self): 17 print('%s move forward' %self.nickname) 18 19 def attack(self,enemy): 20 enemy.life_value-=self.aggressivity 21 class Garen(Hero): 22 pass 23 24 class Riven(Hero): 25 pass 26 27 g1=Garen('草丛伦',100,300) 28 r1=Riven('锐雯雯',57,200) 29 30 print(g1.life_value) 31 r1.attack(g1) 32 print(g1.life_value) 33 34 ''' 35 运行结果 36 300 37 243 38 '''
ps.用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,又称软件重用。不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大。
像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。(后续会详细在继承的实现原理中说明)
1 class Riven(Hero): 2 camp='Noxus' 3 def __init__(self,nickname,aggressivity,life_value,skin): 4 Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能 5 self.skin=skin #新属性 6 def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类 7 Hero.attack(self,enemy) #调用功能 8 print('from riven') 9 def fly(self): #在自己这里定义新的 10 print('%s is flying' %self.nickname) 11 12 r1=Riven('锐雯雯',57,200,'比基尼') 13 r1.fly() 14 print(r1.skin) 15 16 ''' 17 运行结果 18 锐雯雯 is flying 19 比基尼 20 21 '''
4、组合与重用性(重要)
a.软件重用的重要方式除了继承之外还有另外一种方式,即:组合。组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合。
b.组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同:当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
1)继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师。
1 >>> class Teacher: 2 ... def __init__(self,name,gender): 3 ... self.name=name 4 ... self.gender=gender 5 ... def teach(self): 6 ... print('teaching') 7 ... 8 >>> 9 >>> class Professor(Teacher): 10 ... pass 11 ... 12 >>> p1=Professor('egon','male') 13 >>> p1.teach() 14 teaching
2)组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程。
1 class BirthDate: 2 def __init__(self,year,month,day): 3 self.year=year 4 self.month=month 5 self.day=day 6 7 class Couse: 8 def __init__(self,name,price,period): 9 self.name=name 10 self.price=price 11 self.period=period 12 13 class Teacher: 14 def __init__(self,name,gender): 15 self.name=name 16 self.gender=gender 17 def teach(self): 18 print('teaching') 19 class Professor(Teacher): 20 def __init__(self,name,gender,birth,course): 21 Teacher.__init__(self,name,gender) 22 self.birth=birth 23 self.course=course 24 25 p1=Professor('egon','male', 26 BirthDate('1995','1','27'), 27 Couse('python','28000','4 months')) 28 29 print(p1.birth.year,p1.birth.month,p1.birth.day) 30 print(p1.course.name,p1.course.price,p1.course.period) 31 ''' 32 运行结果: 33 1995 1 27 34 python 28000 4 months 35 '''
5、接口与归一化设计
什么是接口?
1 =================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java 2 /* 3 * Java的Interface很好的体现了我们前面分析的接口的特征: 4 * 1)是一组功能的集合,而不是一个功能 5 * 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作 6 * 3)接口只定义函数,但不涉及函数实现 7 * 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */ 8 9 package com.oo.demo; 10 public interface IAnimal { 11 public void eat(); 12 public void run(); 13 public void sleep(); 14 public void speak(); 15 } 16 17 =================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口 18 package com.oo.demo; 19 public class Pig implements IAnimal{ //如下每个函数都需要详细实现 20 public void eat(){ 21 System.out.println("Pig like to eat grass"); 22 } 23 24 public void run(){ 25 System.out.println("Pig run: front legs, back legs"); 26 } 27 28 public void sleep(){ 29 System.out.println("Pig sleep 16 hours every day"); 30 } 31 32 public void speak(){ 33 System.out.println("Pig can not speak"); } 34 } 35 36 =================第三部分:Person2.java 37 /* 38 *实现了IAnimal的“人”,有几点说明一下: 39 * 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样 40 * 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */ 41 42 package com.oo.demo; 43 public class Person2 implements IAnimal { 44 public void eat(){ 45 System.out.println("Person like to eat meat"); 46 } 47 48 public void run(){ 49 System.out.println("Person run: left leg, right leg"); 50 } 51 52 public void sleep(){ 53 System.out.println("Person sleep 8 hours every dat"); 54 } 55 56 public void speak(){ 57 System.out.println("Hellow world, I am a person"); 58 } 59 } 60 61 =================第四部分:Tester03.java 62 package com.oo.demo; 63 64 public class Tester03 { 65 public static void main(String[] args) { 66 System.out.println("===This is a person==="); 67 IAnimal person = new Person2(); 68 person.eat(); 69 person.run(); 70 person.sleep(); 71 person.speak(); 72 73 System.out.println("\n===This is a pig==="); 74 IAnimal pig = new Pig(); 75 pig.eat(); 76 pig.run(); 77 pig.sleep(); 78 pig.speak(); 79 } 80 }
继承有两种用途:
a.继承基类的方法,并且做出自己的改变或者扩展(代码重用)
b.声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
1 class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 2 def read(self): #定接口函数read 3 pass 4 5 def write(self): #定义接口函数write 6 pass 7 8 9 class Txt(Interface): #文本,具体实现read和write 10 def read(self): 11 print('文本数据的读取方法') 12 13 def write(self): 14 print('文本数据的读取方法') 15 16 class Sata(Interface): #磁盘,具体实现read和write 17 def read(self): 18 print('硬盘数据的读取方法') 19 20 def write(self): 21 print('硬盘数据的读取方法') 22 23 class Process(Interface): 24 def read(self): 25 print('进程数据的读取方法') 26 27 def write(self): 28 print('进程数据的读取方法')
ps.
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
为何要用接口?
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
6、抽象类(重要):简单理解就是给类定义一个规范格式
a.抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。python和java都需要借助模块实现。
b.如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的。
c.在python中实现抽象类
1 ''' 2 6、抽象类 3 ''' 4 import abc #利用abc模块实现抽象类 5 6 class All_file(metaclass=abc.ABCMeta): 7 all_type='file' 8 @abc.abstractmethod #定义抽象方法,无需实现功能 9 def read(self): 10 '子类必须定义读功能' 11 pass 12 13 @abc.abstractmethod #定义抽象方法,无需实现功能 14 def write(self): 15 '子类必须定义写功能' 16 pass 17 18 # class Txt(All_file): 19 # pass 20 # 21 # t1=Txt() #报错,子类没有定义抽象方法 22 23 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 24 def read(self): 25 print('文本数据的读取方法') 26 27 def write(self): 28 print('文本数据的读取方法') 29 30 class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 31 def read(self): 32 print('硬盘数据的读取方法') 33 34 def write(self): 35 print('硬盘数据的读取方法') 36 37 class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 38 def read(self): 39 print('进程数据的读取方法') 40 41 def write(self): 42 print('进程数据的读取方法') 43 44 wenbenwenjian=Txt() 45 46 yingpanwenjian=Sata() 47 48 jinchengwenjian=Process() 49 50 #这样大家都是被归一化了,也就是一切皆文件的思想 51 wenbenwenjian.read() 52 yingpanwenjian.write() 53 jinchengwenjian.read() 54 55 print(wenbenwenjian.all_type) 56 print(yingpanwenjian.all_type) 57 print(jinchengwenjian.all_type) 58 ''' 59 结果: 60 文本数据的读取方法 61 硬盘数据的读取方法 62 进程数据的读取方法 63 file 64 file 65 file 66 '''
d.抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。
7、 继承实现的原理(继承顺序)
a.继承顺序
1 class A(object): 2 def test(self): 3 print('from A') 4 5 class B(A): 6 def test(self): 7 print('from B') 8 9 class C(A): 10 def test(self): 11 print('from C') 12 13 class D(B): 14 def test(self): 15 print('from D') 16 17 class E(C): 18 def test(self): 19 print('from E') 20 21 class F(D,E): 22 # def test(self): 23 # print('from F') 24 pass 25 f1=F() 26 f1.test() 27 print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 28 29 #新式类继承顺序:F->D->B->E->C->A 30 #经典类继承顺序:F->D->B->A->E->C 31 #python3中统一都是新式类 32 #pyhon2中才分新式类与经典类
b.python的继承原理
对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:
1 >>> F.mro() #等同于F.__mro__ 2 [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查(从左到右)
3.如果对下一个类存在两个合法的选择,选择第一个父类
8、子类中调用父类方法(建议用super)
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法。
a.父类名.父类方法()
1 class Vehicle: #定义交通工具类 2 Country='China' 3 def __init__(self,name,speed,load,power): 4 self.name=name 5 self.speed=speed 6 self.load=load 7 self.power=power 8 9 def run(self): 10 print('开动啦...') 11 12 class Subway(Vehicle): #地铁 13 def __init__(self,name,speed,load,power,line): 14 Vehicle.__init__(self,name,speed,load,power) 15 self.line=line 16 17 def run(self): 18 print('地铁%s号线欢迎您' %self.line) 19 Vehicle.run(self) 20 21 line13=Subway('中国地铁','180m/s','1000人/箱','电',13) 22 line13.run()
b.super()
1 class Vehicle: #定义交通工具类 2 Country='China' 3 def __init__(self,name,speed,load,power): 4 self.name=name 5 self.speed=speed 6 self.load=load 7 self.power=power 8 9 def run(self): 10 print('开动啦...') 11 12 class Subway(Vehicle): #地铁 13 def __init__(self,name,speed,load,power,line): 14 #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) 15 super().__init__(name,speed,load,power) 16 self.line=line 17 18 def run(self): 19 print('地铁%s号线欢迎您' %self.line) 20 super(Subway,self).run() 21 22 class Mobike(Vehicle):#摩拜单车 23 pass 24 25 line13=Subway('中国地铁','180m/s','1000人/箱','电',13) 26 line13.run()
c.对比
1 #每个类中都继承了且重写了父类的方法 2 class A: 3 def __init__(self): 4 print('A的构造方法') 5 class B(A): 6 def __init__(self): 7 print('B的构造方法') 8 A.__init__(self) 9 10 11 class C(A): 12 def __init__(self): 13 print('C的构造方法') 14 A.__init__(self) 15 16 17 class D(B,C): 18 def __init__(self): 19 print('D的构造方法') 20 B.__init__(self) 21 C.__init__(self) 22 23 pass 24 f1=D() 25 26 print(D.__mro__) #python2中没有这个属性 27 ''' 28 结果 29 D的构造方法 30 B的构造方法 31 A的构造方法 32 C的构造方法 33 A的构造方法 34 (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 35 '''
1 #每个类中都继承了且重写了父类的方法 2 class A: 3 def __init__(self): 4 print('A的构造方法') 5 class B(A): 6 def __init__(self): 7 print('B的构造方法') 8 super(B,self).__init__() 9 10 11 class C(A): 12 def __init__(self): 13 print('C的构造方法') 14 super(C,self).__init__() 15 16 17 class D(B,C): 18 def __init__(self): 19 print('D的构造方法') 20 super(D,self).__init__() 21 22 f1=D() 23 24 print(D.__mro__) #python2中没有这个属性 25 ''' 26 结果: 27 D的构造方法 28 B的构造方法 29 C的构造方法 30 A的构造方法 31 (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 32 '''
四、多态与多态性
1、多态:多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
a. 序列类型有多种形态:字符串,列表,元组。
b. 动物有多种形态:人,狗,猪
1 import abc 2 class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 3 @abc.abstractmethod 4 def talk(self): 5 pass 6 7 class People(Animal): #动物的形态之一:人 8 def talk(self): 9 print('say hello') 10 11 class Dog(Animal): #动物的形态之二:狗 12 def talk(self): 13 print('say wangwang') 14 15 class Pig(Animal): #动物的形态之三:猪 16 def talk(self): 17 print('say aoao')
c.文件有多种形态:文本文件,可执行文件
1 import abc 2 class File(metaclass=abc.ABCMeta): #同一类事物:文件 3 @abc.abstractmethod 4 def click(self): 5 pass 6 7 class Text(File): #文件的形态之一:文本文件 8 def click(self): 9 print('open file') 10 11 class ExeFile(File): #文件的形态之二:可执行文件 12 def click(self): 13 print('execute file')
2、多态性:是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同功能的函数
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
多态性分为静态多态性和动态多态性:
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:
def func(obj): # 参数obj就是多态性的具体表现形式
print(obj.__len__())
func('hello')
func([1,2,3])
1 >>> def func(animal): #参数animal就是对态性的体现 2 ... animal.talk() 3 ... 4 >>> people1=People() #产生一个人的对象 5 >>> pig1=Pig() #产生一个猪的对象 6 >>> dog1=Dog() #产生一个狗的对象 7 >>> func(people1) 8 say hello 9 >>> func(pig1) 10 say aoao 11 >>> func(dog1) 12 say wangwang
1 >>> def func(f): 2 ... f.click() 3 ... 4 >>> t1=Text() 5 >>> e1=ExeFile() 6 >>> func(t1) 7 open file 8 >>> func(e1) 9 execute file
综上我们也可以说,多态性是‘一个接口(函数func),多种实现(如f.click())
多态性的好处:增加了程序的灵活性、增加了程序额可扩展性
a.增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
b.增加了程序额可扩展性:通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
1 >>> class Cat(Animal): #属于动物的另外一种形态:猫 2 ... def talk(self): 3 ... print('say miao') 4 ... 5 >>> def func(animal): #对于使用者来说,自己的代码根本无需改动 6 ... animal.talk() 7 ... 8 >>> cat1=Cat() #实例出一只猫 9 >>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 10 say miao 11 12 ''' 13 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1) 14 '''
五、封装
1、封装的分类:封装数据、封装方法
2、封装的意义:
封装不是单纯意义的隐藏:
a.封装数据的主要原因是:保护隐私
b.封装方法的主要原因是:隔离复杂度(如:相机的快门,对用户使用按一下,对作者来说,需要很多功能来实现用户的这一个动作)
ps.在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
3、封装的两个层面
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口。(接口就是建立在封装的基础上)
a.第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。
1 >>> r1.nickname 2 '草丛伦' 3 >>> Riven.camp 4 'Noxus'
ps.对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口
b.第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
1)在python中用双下划线的方式实现隐藏属性(设置成私有的)
2)类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
3)这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
4)在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
5)对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
1 class A: 2 __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N 3 def __init__(self): 4 self.__X=10 #变形为self._A__X 5 def __foo(self): #变形为_A__foo 6 print('from A') 7 def bar(self): 8 self.__foo() #只有在类内部才可以通过__foo的形式访问到.
1 #隐藏,并对外提供接口,接口附加类型检查逻辑 2 3 class Teacher: 4 def __init__(self,name,age): 5 self.__name=name 6 self.__age=age 7 8 def tell_info(self): 9 print('姓名:%s,年龄:%s' %(self.__name,self.__age)) 10 def set_info(self,name,age): 11 if not isinstance(name,str): 12 raise TypeError('姓名必须是字符串类型') 13 if not isinstance(age,int): 14 raise TypeError('年龄必须是整型') 15 self.__name=name 16 self.__age=age 17 18 t=Teacher('egon',18) 19 t.tell_info() 20 21 t.set_info('egon',19) 22 t.tell_info()
ps.这种方式的注意事项:
1)这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
1 >>> a=A() 2 >>> a._A__N 3 0 4 >>> a._A__X 5 10 6 >>> A._A__N 7 0
2)变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3)在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
1 #正常情况 2 >>> class A: 3 ... def fa(self): 4 ... print('from A') 5 ... def test(self): 6 ... self.fa() 7 ... 8 >>> class B(A): 9 ... def fa(self): 10 ... print('from B') 11 ... 12 >>> b=B() 13 >>> b.test() 14 from B 15 16 #把fa定义成私有的,即__fa 17 >>> class A: 18 ... def __fa(self): #在定义时就变形为_A__fa 19 ... print('from A') 20 ... def test(self): 21 ... self.__fa() #只会与自己所在的类为准,即调用_A__fa 22 ... 23 >>> class B(A): 24 ... def __fa(self): 25 ... print('from B') 26 ... 27 >>> b=B() 28 >>> b.test() 29 from A
ps.python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的。python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__,详见面向对象高级
4、property(特性):property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。(相当于把函数的返回值,当作静态变量调用)
a.BMI指数(肥胖指数)示例:
1 ''' 2 成人的BMI数值: 3 过轻:低于18.5 4 正常:18.5-23.9 5 过重:24-27 6 肥胖:28-32 7 非常肥胖, 高于32 8 体质指数(BMI)=体重(kg)÷身高^2(m) 9 EX:70kg÷(1.75×1.75)=22.86 10 ''' 11 class People: 12 def __init__(self,name,weight,height): 13 self.name=name 14 self.weight=weight 15 self.height=height 16 @property 17 def bmi(self): 18 return self.weight / (self.height**2) 19 20 p1=People('egon',75,1.85) 21 print(p1.bmi)
b.圆的周长和面积示例:
1 import math 2 class Circle: 3 def __init__(self,radius): #圆的半径radius 4 self.radius=radius 5 6 @property 7 def area(self): 8 return math.pi * self.radius**2 #计算面积 9 10 @property 11 def perimeter(self): 12 return 2*math.pi*self.radius #计算周长 13 14 c=Circle(10) 15 print(c.radius) 16 print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 17 print(c.perimeter) #同上 18 ''' 19 输出结果: 20 10 21 314.1592653589793 22 62.83185307179586 23 '''
ps.此时的特性arear和perimeter不能被赋值.
1 c.area=3 #为特性area赋值 2 ''' 3 抛出异常: 4 AttributeError: can't set attribute 5 '''
c.什么情况下使用property?
1)将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。
2)面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现。
1 class Foo: 2 def __init__(self,val): 3 self.__NAME=val #将所有的数据属性都隐藏起来 4 5 @property 6 def name(self): 7 return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) 8 9 @name.setter # 私有方法设置 10 def name(self,value): 11 if not isinstance(value,str): #在设定值之前进行类型检查 12 raise TypeError('%s must be str' %value) # 返回自定义错误 13 self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME 14 15 @name.deleter # 私有方法删除方法 16 def name(self): 17 raise TypeError('Can not delete') # 返回自定义错误 18 19 f=Foo('egon') 20 print(f.name) 21 # f.name=10 #抛出异常'TypeError: 10 must be str' 22 del f.name #抛出异常'TypeError: Can not delete'
1 # 古老的用法 2 class Foo: 3 def __init__(self,val): 4 self.__NAME=val #将所有的数据属性都隐藏起来 5 6 def getname(self): 7 return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) 8 9 def setname(self,value): 10 if not isinstance(value,str): #在设定值之前进行类型检查 11 raise TypeError('%s must be str' %value) 12 self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME 13 14 def delname(self): 15 raise TypeError('Can not delete') 16 17 name=property(getname,setname,delname) #不如装饰器的方式清晰
5、封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
1 #类的设计者 2 class Room: 3 def __init__(self,name,owner,width,length,high): 4 self.name=name 5 self.owner=owner 6 self.__width=width 7 self.__length=length 8 self.__high=high 9 def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 10 return self.__width * self.__length 11 12 使用者 13 >>> r1=Room('卧室','egon',20,20,20) 14 >>> r1.tell_area() #使用者调用接口tell_area 15 400
1 #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 2 class Room: 3 def __init__(self,name,owner,width,length,high): 4 self.name=name 5 self.owner=owner 6 self.__width=width 7 self.__length=length 8 self.__high=high 9 def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 10 return self.__width * self.__length * self.__high 11 12 >>> r1.tell_area() 13 8000
六、绑定与非绑定
1、绑定方法:绑定给谁,谁来调用就自动将它本身当作第一个参数传入。
a.绑定到类的方法:用classmethod装饰器装饰的方法。为类量身定制, 类.boud_method(),自动将类当作第一个参数传入。(其实对象也可调用,但仍将类当作第一个参数传入)
b.绑定到对象的方法:没有被任何装饰器装饰的方法。为对象量身定制,对象.boud_method(),自动将对象当作第一个参数传入。(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
c.classmethod使用:classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法。
1 HOST='127.0.0.1' 2 PORT=3306 3 DB_PATH=r'C:\Users\Administrator\PycharmProjects\test\面向对象编程\test1\db'
1 import settings 2 import hashlib # 导入hashlib 3 import time # 导入时间 4 class MySQL: 5 def __init__(self,host,port): # 定义构建方法 6 self.host=host 7 self.port=port 8 9 @classmethod 10 def from_conf(cls): # 根据对象的类名判断执行那个类,传那个值 11 print(cls) 12 return cls(settings.HOST,settings.PORT) 13 14 print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>> 15 conn=MySQL.from_conf() 16 17 print(conn.host,conn.port) 18 conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类
2、非绑定方法:用staticmethod装饰器装饰的方法。
a.不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已.
ps.注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说。
b.staticmethod:tatimethod不与类或对象绑定,谁都可以调用,没有自动传值效果,python为我们内置了函数staticmethod来把类中的函数定义成静态方法。
1 import hashlib 2 import time 3 class MySQL: 4 def __init__(self,host,port): 5 self.id=self.create_id() 6 self.host=host 7 self.port=port 8 @staticmethod # 静态方法不需要任何传值,相当于类中的普通函数 9 def create_id(): #就是一个普通工具 10 m=hashlib.md5(str(time.clock()).encode('utf-8')) 11 return m.hexdigest() 12 13 14 print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数 15 conn=MySQL('127.0.0.1',3306) 16 print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数
3、练习:
a.staticmethod和classmethod的区别
1 import settings 2 class MySQL: 3 def __init__(self,host,port): 4 self.host=host 5 self.port=port 6 # @staticmethod # 非绑定 7 # def from_conf(): 8 # return MySQL(settings.HOST,settings.PORT) 9 ''' 10 结果为: 11 Mysql>> <function MySQL.__str__ at 0x000000557461CA60> 12 Mariadb>> <function Mariadb.__str__ at 0x000000557461CAE8> 13 m>> <bound method MySQL.__str__ of <__main__.MySQL object at 0x0000005574619A20>> 14 >> 就不告诉你 15 ''' 16 17 @classmethod # 绑定 18 def from_conf(cls): 19 return cls(settings.HOST,settings.PORT) 20 ''' 21 结果: 22 Mysql>> <function MySQL.__str__ at 0x000000555435CA60> 23 Mariadb>> <function Mariadb.__str__ at 0x000000555435CAE8> 24 m>> <bound method Mariadb.__str__ of <__main__.Mariadb object at 0x0000005554359A20>> 25 >> 主机:127.0.0.1 端口:3306 26 ''' 27 28 def __str__(self): 29 return '就不告诉你' 30 31 32 class Mariadb(MySQL): 33 def __str__(self): # 自定义__str__ 34 return '主机:%s 端口:%s' %(self.host,self.port) 35 36 37 m=Mariadb.from_conf() 38 print('Mysql>>',MySQL.__str__) # __str__ 为对象调用类名 39 print('Mariadb>>',Mariadb.__str__) # __str__ 为对象调用类名 40 print('m>>', m.__str__) # __str__ 为对象调用类名 41 print('>>',m) #我们的意图是想触发Mariadb.__str__,但是结果触发了MySQL.__str__的执行,打印就不告诉你:
b.定义MySQL类
1.对象有id、host、port三个属性
2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一
3.提供两种实例化方式,方式一:用户传入host和port 方式二:从配置文件中读取host和port进行实例化
4.为对象定制方法,save和get,save能自动将对象序列化到文件中,文件名为id号,文件路径为配置文件中DB_PATH;get方法用来从文件中反序列化出对象
1 #settings.py内容 2 ''' 3 HOST='127.0.0.1' 4 PORT=3306 5 DB_PATH=r'D:/old_boy/old_boy_17_07/db' 6 ''' 7 import settings 8 import hashlib 9 import time 10 import random 11 import pickle 12 import os 13 class MySQL: 14 def __init__(self,host,port): 15 self.id = self.create_id() 16 self.host = host 17 self.port = port 18 def save(self): 19 file_path=r'%s%s%s' %(settings.DB_PATH,os.sep,self.id) 20 pickle.dump(self,open(file_path,'wb')) 21 22 def get(self): 23 file_path = r'%s%s%s' % (settings.DB_PATH, os.sep, self.id) 24 return pickle.load(open(file_path,'rb')) 25 26 @staticmethod 27 def create_id(): 28 m=hashlib.md5(str(time.clock()).encode('utf-8')) #查看clock源码注释,指的是cpu真实时间,不要用time.time(),否则会出现id重复 29 return m.hexdigest() 30 @classmethod 31 def from_conf(cls): 32 print(cls) 33 return cls(settings.HOST,settings.PORT) 34 35 # print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>> 36 conn=MySQL.from_conf() 37 # print(conn.id) 38 39 print(conn.create_id()) 40 print(MySQL.create_id()) 41 42 # print(conn.id) 43 # conn.save() 44 # obj=conn.get() 45 # print(obj.id) 46 ''' 47 <class '__main__.MySQL'> 48 adcc7229a441e073a5cc775b824597e0 49 991f3825a750ca50348659f095d619ac 50 '''
c.时间类型
1 # 时间无法触发__str__属性 2 # class Date: 3 # def __init__(self,year,month,day): 4 # self.year=year 5 # self.month=month 6 # self.day=day 7 # @staticmethod 8 # def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间 9 # t = time.localtime() #获取结构化的时间格式 10 # return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回 11 # @staticmethod 12 # def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间 13 # t = time.localtime(time.time()+86400) 14 # return Date(t.tm_year,t.tm_mon,t.tm_mday) 15 # 16 # a=Date('1987',11,27) #自己定义时间 17 # b=Date.now() #采用当前时间 18 # c=Date.tomorrow() #采用明天的时间 19 # 20 # print(a.year,a.month,a.day) 21 # print(b.year,b.month,b.day) 22 # print(c.year,c.month,c.day) 23 24 # 解决方法 25 #分割线============================== 26 # import time 27 # class Date: 28 # def __init__(self,year,month,day): 29 # self.year=year 30 # self.month=month 31 # self.day=day 32 # @staticmethod 33 # def now(): 34 # t=time.localtime() 35 # return Date(t.tm_year,t.tm_mon,t.tm_mday) 36 # 37 # class EuroDate(Date): 38 # def __str__(self): 39 # return 'year:%s month:%s day:%s' %(self.year,self.month,self.day) 40 # 41 # e=EuroDate.now() 42 # print(e) #我们的意图是想触发EuroDate.__str__,但是结果为 43 ''' 44 输出结果: 45 <__main__.Date object at 0x00000009FA669A58> 46 ''' 47 48 49 # 因为e就是用Date类产生的,所以根本不会触发EuroDate.__str__,解决方法就是用classmethod 50 # import time 51 # class Date: 52 # def __init__(self,year,month,day): 53 # self.year=year 54 # self.month=month 55 # self.day=day 56 # # @staticmethod 57 # # def now(): 58 # # t=time.localtime() 59 # # return Date(t.tm_year,t.tm_mon,t.tm_mday) 60 # 61 # @classmethod #改成类方法 62 # def now(cls): 63 # t=time.localtime() 64 # return cls(t.tm_year,t.tm_mon,t.tm_mday) #哪个类来调用,即用哪个类cls来实例化 65 # 66 # class EuroDate(Date): 67 # def __str__(self): 68 # return 'year:%s month:%s day:%s' %(self.year,self.month,self.day) 69 # 70 # e=EuroDate.now() 71 # print(e) #我们的意图是想触发EuroDate.__str__,此时e就是由EuroDate产生的,所以会如我们所愿 72 ''' 73 输出结果: 74 year:2017 month:6 day:13 75 '''
七、面向对象的软件开发
转自:http://www.cnblogs.com/linhaifeng/articles/6182264.html
很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的。
所以软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程
面向对象的软件工程包括下面几个部:
1.面向对象分析(object oriented analysis ,OOA)
a.软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
b.建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2 面向对象设计(object oriented design,OOD)
a.根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
b.首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
c.在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3 面向对象编程(object oriented programming,OOP)
a.根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4 面向对象测试(object oriented test,OOT)
a.在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
b.面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5 面向对象维护(object oriendted soft maintenance,OOSM)
a.正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
b.由于使用了面向对象的方法开发程序,使用程序的维护比较容易。
c.因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。
在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。
现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。
在一个大型软件开发过程中,OOP只是很小的一个部分。
对于全栈开发来说,这五个阶段都有了,对于简单的问题,不必严格按照这个5个阶段进行,往往由程序设计者按照面向对象的方法进行程序设计,包括类的设计和程序的设计
八、python中关于OOP的常用术语
转自:http://www.cnblogs.com/linhaifeng/articles/6182264.html
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__