python-面向对象进阶
三大特性:继承,多态,封装
1,初识继承
继承指的是类与类之间的关系,是一种什么“是”什么的关系,继承的功能之一就是用来解决代码重用问题。
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可以成为基类或超类,新建的类称为派生类或子类。
1 # 父类/基类/超生类 2 # 子类/派生类(继承父类) 3 # _bases_则是查看所有继承的父类
代码示例如下:
1 class ParentClass1: 2 pass 3 class ParentClass2: 4 pass 5 class SubClass1(ParentClass1): 6 pass 7 class SubClass2(ParentClass1,ParentClass2): 8 pass 9 10 print(SubClass1.__bases__) 11 print(SubClass2.__bases__) 12 13 ### 14 (<class '__main__.ParentClass1'>,) 15 (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
2,继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
3, 继承与重用性
继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
1 class Hero: 2 def __init__(self, nickname, life_value, aggressivity): 3 self.nickname = nickname 4 self.life_value = life_value 5 self.aggressivity = aggressivity 6 def attack(self, enemy): 7 enemy.life_value -= self.aggressivity 8 9 class Gailen(Hero): 10 pass 11 12 class Riven(Hero): 13 pass 14 15 gailen = Gailen('草丛伦', 120, 40) 16 print(gailen.nickname, gailen.life_value, gailen.aggressivity) # 这里能实现,说明了它是继承了Hero的属性
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大节省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
再说属性查找
查找顺序:对象自己 → 对象自己的类 → 父类
1 # 查找顺序:对象自己 → 对象自己的类 → 父类 2 class Foo: 3 def f1(self): 4 print('Foo.f1') 5 6 def f2(self): 7 print('Foo.f2') 8 self.f1() # b.f1() 9 10 class Bar(Foo): 11 def f1(self): 12 print('Bar.f1') 13 14 b = Bar() 15 b.f2() 16 ###Foo.f2 17 ###Bar.f1
4,派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
1 class Hero: 2 def __init__(self, nickname, life_value, aggressivity): 3 self.nickname = nickname 4 self.life_value = life_value 5 self.aggressivity = aggressivity 6 def attack(self, enemy): 7 enemy.life_value -= self.aggressivity 8 9 class Riven(Hero): 10 camp='Noxus' 11 def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类 12 print('from riven') 13 def fly(self): #在自己这里定义新的 14 print('%s is flying' % self.nickname)
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要其传值。
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 '''
5,继承的实现原理
1,方法解析顺序(MRO)列表
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
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 """ 30 from D 31 (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 32 """
2,区别新式类和经典类
1 # 在python2中-->经典类:没有继承object的类,以及它的子类都称经典类 2 class Foo: 3 pass 4 class Bar(Foo): 5 pass 6 7 # 在python2中-->新式类:继承object的类,以及它的子类都称之为新式类 8 class Foo(object): 9 pass 10 class Bar(Foo): 11 pass 12 13 # 在python3中-->新式类:一个类没有继承object类,默认就继承了object 14 class Foo(): # --> class Foo(object): 15 pass 16 print(Foo.__bases__) 17 18 ### 19 (<class 'object'>,)
3,深度优先和广度优先方式查找
从左边开始就一直走到底,然后后面再这样一直轮下去
新式类不会走到头,快要到头的时候折返回来往另外一个找,最后一个爹一条路走到底
代码示例
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中才分新式类与经典类 33 ''' 34 print(F._mro_)执行结果 35 36 1 37 (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 38 '''
4,子类重用父类的方法或属性
在子类派生出的新方法中,往往需要重用父类的方法,我们有两种方式实现
1,法一:指名道姓(不依赖继承)
指名道姓,不依赖继承,即父类名.父类方法()
1 class Hero: 2 def __init__(self, nickname, life_value, aggressivity): 3 self.nickname = nickname 4 self.life_value = life_value 5 self.aggressivity = aggressivity 6 def attack(self, enemy): 7 enemy.life_value -= self.aggressivity 8 9 class Gailen(Hero): 10 camp = 'Demacia' 11 def attack(self, enemy): 12 Hero.attack(self, enemy) # 指名道姓 13 print('from Gailen Class') 14 15 class Riven(Hero): 16 camp = 'Noxus' 17 18 gailen = Gailen('草丛伦', 100, 30) 19 riven = Riven('锐雯雯', 80, 50) 20 print(riven.life_value) 21 gailen.attack(riven) 22 print(riven.life_value) 23 24 """ 25 80 26 from Gailen Class 27 50 28 """
1 class Hero: 2 def __init__(self, nickname, life_value, aggressivity): 3 self.nickname = nickname 4 self.life_value = life_value 5 self.aggressivity = aggressivity 6 def attack(self, enemy): 7 enemy.life_value -= self.aggressivity 8 9 class Gailen(Hero): 10 camp = 'Demacia' 11 def __init__(self, nickname, life_value, aggressivity, weapon): 12 # self.nickname = nickname 13 # self.life_value = life_value 14 # self.aggressivity = aggressivity 15 Hero.__init__(self, nickname, life_value, aggressivity) 16 17 self.weapon = weapon 18 def attack(self, enemy): 19 Hero.attack(self, enemy) # 指名道姓 20 print('from Gailen Class') 21 22 gailen = Gailen('草丛伦', 100, 30, '大宝剑') 23 print(gailen.__dict__) 24 25 """ 26 {'nickname': '草丛伦', 'life_value': 100, 'aggressivity': 30, 'weapon': '大宝剑'} 27 """
2,法二:super()(依赖继承)
super(),依赖继承
1 # 方式二 2 class Hero: 3 def __init__(self, nickname, life_value, aggressivity): 4 self.nickname = nickname 5 self.life_value = life_value 6 self.aggressivity = aggressivity 7 def attack(self, enemy): 8 enemy.life_value -= self.aggressivity 9 10 class Gailen(Hero): 11 camp = 'Demacia' 12 def attack(self, enemy): 13 super(Gailen, self).attack(enemy) # 依赖继承,super(自己的类名,self) 14 print('from Gailen Class') 15 16 class Riven(Hero): 17 camp = 'Noxus' 18 19 gailen = Gailen('草丛伦', 100, 30) 20 riven = Riven('锐雯雯', 80, 50) 21 22 print(riven.life_value) 23 gailen.attack(riven) 24 print(riven.life_value) 25 26 """ 27 80 28 from Gailen Class 29 50 30 """
在python3中super()中已经默认传入参数,可以不用传参数
1 class Hero: 2 def __init__(self, nickname, life_value, aggressivity): 3 self.nickname = nickname 4 self.life_value = life_value 5 self.aggressivity = aggressivity 6 def attack(self, enemy): 7 enemy.life_value -= self.aggressivity 8 9 class Gailen(Hero): 10 camp = 'Demacia' 11 def __init__(self, nickname, life_value, aggressivity, weapon): 12 # self.nickname = nickname 13 # self.life_value = life_value 14 # self.aggressivity = aggressivity 15 super().__init__(nickname, life_value, aggressivity) # 在python3中默认可以不用在super中写入参数 16 17 self.weapon = weapon 18 def attack(self, enemy): 19 Hero.attack(self, enemy) # 指名道姓 20 print('from Gailen Class') 21 22 gailen = Gailen('草丛伦', 100, 30, '大宝剑') 23 print(gailen.__dict__) 24 25 """ 26 {'nickname': '草丛伦', 'life_value': 100, 'aggressivity': 30, 'weapon': '大宝剑'} 27 """
,super()依赖mro列表查找
这两种方式的区别是:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super仍然会按照mro继续往后查找。
1 # A没有继承B,但是A内super会基于C.mro()继续往后找 2 class A: 3 def f1(self): 4 print('from A') 5 super().f1() 6 class B: 7 def f1(self): 8 print('from B') 9 class C(A,B): 10 pass 11 12 print(C.mro()) 13 c = C() 14 c.f1() # 打印结果:from B 15 16 """ 17 [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>] 18 from A 19 from B 20 """
6,组合(类的组合)
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
1,组合与重用性
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同。
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,这时用组合比较好。
代码示例
1 class People: 2 def __init__(self, name, age, sex): 3 self.name = name 4 self.age = age 5 self.sex = sex 6 7 class Teacher(People): 8 def __init__(self, name, age, sex, level, salary): 9 super().__init__(name, age, sex) 10 self.level = level 11 self.salary = salary 12 13 class Student(People): 14 def __init__(self, name, age, sex, class_time): 15 super().__init__(name, age, sex) 16 self.class_time = class_time 17 18 class Course: 19 def __init__(self, course_name, course_price, course_period): 20 self.course_name = course_name 21 self.course_price = course_price 22 self.course_period = course_period 23 def tell_info(self): 24 print('课程名为<%s> 课程价钱为<%s> 课程周期为<%s>' % (self.course_name, self.course_price, self.course_period)) 25 26 class Date: 27 def __init__(self, year, mon, day): 28 self.year = year 29 self.mon = mon 30 self.day = day 31 def tell_info(self): 32 print('I was born in %s年%s月%s日' % (self.year, self.mon, self.day)) 33 34 teacher1 = Teacher('luoma', 28, 'male', 10, 3000) 35 teacher2 = Teacher('laoluo', 38, 'male', 30, 3000) 36 python = Course('python', 3000, '3mons') 37 linux = Course('linux', 2000, '4mons') 38 39 teacher1.course = python 40 teacher2.course = python 41 42 43 print(python) 44 print(teacher1.course) # teacher1.course == python 45 print(teacher2.course) 46 print(teacher1.course.course_name) # teacher1.course.course_name == Course.course_name 47 48 teacher1.course.tell_info() 49 50 student1 = Student('laoqiu', 18, 'male', 8) 51 52 # 给老师这个对象定制了课程属性,让这个属性指向另一个对象,将老师类和课程类组合到一起 53 student1.course1 = python 54 student1.course2 = linux 55 student1.course1.tell_info() 56 student1.course2.tell_info() 57 58 student1.courses = [] 59 student1.courses.append(python) 60 student1.courses.append(linux) 61 print(student1.courses) 62 63 # 创建一个学生对象和一个时间类,可以达到这种效果 64 d = Date(1999, 4, 20) 65 student1.birth = d 66 print(student1.birth.tell_info()) 67 print(student1.course1.tell_info()) 68 69 """ 70 <__main__.Course object at 0x02CECCF0> 71 <__main__.Course object at 0x02CECCF0> 72 <__main__.Course object at 0x02CECCF0> 73 python 74 课程名为<python> 课程价钱为<3000> 课程周期为<3mons> 75 课程名为<python> 课程价钱为<3000> 课程周期为<3mons> 76 课程名为<linux> 课程价钱为<2000> 课程周期为<4mons> 77 [<__main__.Course object at 0x02CECCF0>, <__main__.Course object at 0x02CECD10>] 78 I was born in 1999年4月20日 79 None 80 课程名为<python> 课程价钱为<3000> 课程周期为<3mons> 81 None 82 """
7,抽象类与归一化设计
1,什么是接口
你好,给我开个查询接口>>>此时的接口指的是:自己提供给使用者来调用自己功能的方式方法入口,java中的interface使用如下
java中的interface:
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的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,<br>但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样 40 * 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,<br>这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */ 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(" ===This is a pig==="); 74 IAnimal pig = new Pig(); 75 pig.eat(); 76 pig.run(); 77 pig.sleep(); 78 pig.speak(); 79 } 80 } 81 82 java中的interface
2,为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
1,归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2,归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
①就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
②再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
3,模仿interface
在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念
可以借助第三方模块:http://pypi.python.org/pypi/zope.interface
也可以使用继承,其实继承有两种用途
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
二:声明某个子类兼容于某基类,定义一个接口类(模仿java的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('进程数据的读取方法')
4,抽象类
1,什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
2,为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
3,在python中实现抽象类
1 #一切皆文件 2 import abc #利用abc模块实现抽象类 3 4 class All_file(metaclass=abc.ABCMeta): 5 all_type='file' 6 @abc.abstractmethod #定义抽象方法,无需实现功能 7 def read(self): 8 '子类必须定义读功能' 9 pass 10 11 @abc.abstractmethod #定义抽象方法,无需实现功能 12 def write(self): 13 '子类必须定义写功能' 14 pass 15 16 # class Txt(All_file): 17 # pass 18 # 19 # t1=Txt() #报错,子类没有定义抽象方法 20 21 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 22 def read(self): 23 print('文本数据的读取方法') 24 25 def write(self): 26 print('文本数据的读取方法') 27 28 class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 29 def read(self): 30 print('硬盘数据的读取方法') 31 32 def write(self): 33 print('硬盘数据的读取方法') 34 35 class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 36 def read(self): 37 print('进程数据的读取方法') 38 39 def write(self): 40 print('进程数据的读取方法') 41 42 wenbenwenjian=Txt() 43 44 yingpanwenjian=Sata() 45 46 jinchengwenjian=Process() 47 48 #这样大家都是被归一化了,也就是一切皆文件的思想 49 wenbenwenjian.read() 50 yingpanwenjian.write() 51 jinchengwenjian.read() 52 53 print(wenbenwenjian.all_type) 54 print(yingpanwenjian.all_type) 55 print(jinchengwenjian.all_type)
再来通过另外一个例子看看其中的差别和演变
先来看看原代码
1 class People: 2 def walk(self): 3 print('is walking') 4 5 class Pig: 6 def run(self): 7 print('is running') 8 9 class Dog: 10 def jump(self): 11 print('is jumping') 12 13 people1 = People() 14 pig1 = Pig() 15 dog1 = Dog() 16 17 # 下面每个对象都有相应的走的功能,但是需要调用不用的方法。需要找个类把所有的方法统一起来。 18 people1.walk() 19 pig1.run() 20 dog1.jump()
但是,这样会造成的困扰就是会增加后面使用者的使用难度,同样是走的动作,但是却有不同的表达
这时,我们可以使用抽象类,达到归一化
1 # 通过调用模块,装饰器等,可以实现后面的调用一定要调用Animal里面的方法,否则会报错 2 import abc 3 class Animal(metaclass=abc.ABCMeta): # 抽象类只能被继承,不能被实例化,功能是指规范子类 4 all_type = 'animal' 5 @abc.abstractmethod 6 def run(self): 7 pass 8 @abc.abstractmethod 9 def eat(self): 10 pass 11 12 # 抽象类不能被实例化,否则会报错 13 # animal = Animal() # Can't instantiate abstract class Animal with abstract methods eat, run 14 15 class People(Animal): 16 def run(self): 17 print('people is running') 18 def eat(self): 19 print('people is eating') 20 21 class Pig(Animal): 22 def run(self): 23 print('pig is running') 24 def eat(self): 25 print('pig is eating') 26 27 class Dog(Animal): 28 29 def run(self): 30 print('dog is running') 31 def eat(self): 32 print('dog is eating') 33 34 people1 = People() 35 pig1 = Pig() 36 dog1 = Dog() 37 38 people1.run() 39 pig1.run() 40 dog1.run() 41 print(people1.all_type) # 虽然Animal是抽象类,但本质上还是一个类。
4. 抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
8,多态
多态指的是一类事物有多种形态,比如动物有多种形态:人,狗,猪
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')
1,多态性
(1)什么是多态动态绑定(在继承的背景下使用,又是也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例,多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符 + 进行运算,1+2=3。
动态多态性:如下
1 peo=People() 2 dog=Dog() 3 pig=Pig() 4 5 #peo、dog、pig都是动物,只要是动物肯定有talk方法 6 #于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 7 peo.talk() 8 dog.talk() 9 pig.talk() 10 11 #更进一步,我们可以定义一个统一的接口来使用 12 def func(obj): 13 obj.talk()
(2)为什么要用多态性(多态性的好处)
其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承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)
2,鸭子类型(理论理解)
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
1 #二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 2 class TxtFile: 3 def read(self): 4 pass 5 6 def write(self): 7 pass 8 9 class DiskFile: 10 def read(self): 11 pass 12 def write(self): 13 pass
例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
1 #str,list,tuple都是序列类型 2 s=str('hello') 3 l=list([1,2,3]) 4 t=tuple((4,5,6)) 5 6 #我们可以在不考虑三者类型的前提下使用s,l,t 7 s.__len__() 8 l.__len__() 9 t.__len__() 10 11 len(s) 12 len(l) 13 len(t)
9,封装
引子
封装有点像拿一个麻袋,把一个东西给装起来,然后封上口子,照这种逻辑来看,封装=‘隐藏’,这种理解还是有点片面
1,先看如何隐藏
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
1 # 其实这仅仅这是一种变形操作 2 # 类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: 3 4 class A: 5 6 __x = 1 # _A__x = 1 # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N 7 8 def __init__(self, name): 9 self.__X = 10 # 变形为self._A__X 10 self.name = name # self._A__name = name 11 12 def __foo(self): # def _A__foo(self): 13 print('from _foo') 14 15 def bar(self): 16 self.__foo() # self._A__foo() # 只有在类内部才可以通过__foo的形式访问到,在类定义的时候,就已经形成了这种格式了 17 print('from bar') 18 19 a = A('egon') 20 21 print(a.__dict__) 22 print(A.__dict__) 23 print(a.bar()) 24 # A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形 25 26 """ 27 执行结果 28 {'_A__X': 10, 'name': 'egon'} 29 {'__module__': '__main__', '_A__x': 1, '__init__': <function A.__init__ at 0x02C1A390>, <br>'_A__foo': <function A.__foo at 0x02C1A3D8>, 'bar': <function A.bar at 0x02C1A420>, <br>'__dict__': <attribute '__dict__' of 'A' objects>, <br>'__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} 30 from _foo 31 from bar 32 None 33 """
1,这种自动变形的特点:
1,在类外部无法直接 obj.__AttrName
2,在类内部是可以直接使用: obj.__AttrName
3,子类无法覆盖父类__开头的属性
1 # 子类无法覆盖父类__开头的属性。 2 class Foo: 3 def __func(self): # _Foo__func 4 print('from foo') 5 6 class Bar(Foo): 7 def __func(self): # _Bar__func # 子类定义的和父类定义的表面上看起来一样,但是却实质上不一样,根本就不是一个名字 8 print('from bar')
2,这种变形需要注意的问题:
1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2、变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
1 class B: 2 def __init__(self, name): 3 self.__name = name 4 b = B('egon') 5 print(b.__dict__) 6 b.__age = 18 7 print(b.__dict__)<br>print(b.__age) 8 9 ### 10 {'_B__name': 'xiong'} 11 {'_B__name': 'xiong', '__age': 18}<br>18
3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
1 # 正常情况 2 class A: 3 def foo(self): 4 print('A.foo') 5 def bar(self): 6 print('A.bar') 7 self.foo() # b.foo(),访问顺序,先访问自己,然后类,再到父类。self.foo(),父类中存在,先访问父类里面的 8 9 class B(A): 10 def foo(self): 11 print('B.foo') 12 13 b = B() 14 b.bar() 15 16 ### 17 A.bar 18 B.foo 19 20 # 定义私有,通过这种方法,调用只能访问类本身里面的变量 21 class A: 22 def __foo(self): # _A__foo,看上去和子类的__foo长得一样,实则在定义的时候,名称就已经发生了改变了 23 print('A.foo') 24 def bar(self): 25 print('A.bar') 26 self.__foo() # self._A__foo() 27 28 class B(A): 29 def __foo(self): # _B__foo 30 print('B.foo') 31 32 b = B() 33 b.bar() 34 35 ### 36 A.bar 37 A.foo
2,封装的意义
1,封装数据属性:
1 # 封装数据属性:明确的区分内外,控制外部对隐藏属性的操作。 2 class People: 3 def __init__(self, name, age): 4 self.__name = name 5 self.__age = age 6 def tell_info(self): 7 print('Name:%s Age:%s' % (self.__name, self.__age)) 8 def set_info(self, name, age): 9 if not isinstance(name, str): # isinstance 什么必须是什么的实例,这里name必须是str 10 print('名字必须是字符串类型') 11 return 12 if not isinstance(age, int): 13 print('年龄必须是整数类型') 14 return 15 self.__name = name 16 self.__age = age 17 18 19 p = People('xiong', 18) 20 print(p._People__name) # 硬要访问 21 p.tell_info() 22 p.set_info('xiong', 17) 23 p.tell_info() 24 25 ### 26 xiong 27 Name:xiong Age:18 28 Name:xiong Age:17
2,封装方法:隔离复杂度
1 # 封装方法:隔离复杂度 2 # 取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 3 # 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 4 # 隔离了复杂度,同时也提升了安全性 5 6 class ATM: 7 def __card(self): 8 print('插卡') 9 def __auth(self): 10 print('用户认证') 11 def __input(self): 12 print('输入取款金额') 13 def __print_bill(self): 14 print('打印账单') 15 def __take_money(self): 16 print('取款') 17 18 def withdraw(self): 19 self.__card() 20 self.__auth() 21 self.__input() 22 self.__print_bill() 23 self.__take_money() 24 25 a = ATM() 26 a.withdraw()
封装方法的其他举例:
- 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
- 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
- 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
3,封装与可拓展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑
1 class Room: 2 def __init__(self, name, owner, weight, length): 3 self.name = name 4 self.owner = owner 5 6 self.__length = length 7 self.__weight = weight 8 def tell_area(self): 9 return self.__weight * self.__length 10 def room_type(self): 11 12 print("%s lives in the %s, the squre is %s" % (self.owner, self.name, self.tell_area())) 13 14 r = Room('豪华公寓', 'xiong', 50, 50) 15 print(r.tell_area()) 16 print(r.room_type())
4,property的使用
什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例一:
BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
1 # 方式一: 2 class People: 3 def __init__(self, name, weight, height): 4 self.name = name 5 self.weight = weight 6 self.height = height 7 8 p = People('xiong',70,1.75) 9 p.bmi = p.weight/(p.height ** 2) 10 print(p.bmi) 11 12 # 方式二:通过函数,有括号,容易给使用者造成误解 13 class People: 14 def __init__(self, name, weight, height): 15 self.name = name 16 self.weight = weight 17 self.height = height 18 def bmi(self): 19 return self.weight / (self.height**2) 20 21 p1 = People('xiong',70,1.75) 22 print(p1.bmi())
1 # 方式三:使用property装饰器特性实现功能 2 # 实现统一访问,将方法伪装起来,bmi不能赋值(报错),且只能return返回。 3 class People: 4 def __init__(self, name, weight, height): 5 self.name = name 6 self.weight = weight 7 self.height = height 8 @property 9 def bmi(self): 10 return self.weight / (self.height**2) 11 12 p1 = People('xiong',75,1.8) 13 print(p1.bmi) 14 # p1.bmi = 1 # AttributeError: can't set attribute
例二:
将一个要通过计算的属性,封装成一个用户访问就像访问一个数据属性就可以的类型,伪装成一个更简单的方法,方便用户使用
class People: def __init__(self, name): self.__name = name def get_name(self): return self.__name p = People('xiong') print(p.get_name()) ### xiong
通过property进行封装:
1 class People: 2 def __init__(self, name): 3 self.__name = name 4 @property # 访问,查看,将一个要通过计算的属性,封装成一个用户访问就像访问一个数据属性就可以了,伪装 5 def name(self): 6 return self.__name 7 @name.setter # 修改 8 def name(self, val): 9 if not isinstance(val, str): 10 print('名字必须是字符串类型') 11 return 12 self.__name = val 13 14 @name.deleter # 删除 15 def name(self): 16 print("deleter") 17 print('不允许删除') 18 p = People('xiong') 19 print(p.name) # 访问行为 20 21 p.name = 'xiong' # 修改行为 22 print(p.name) 23 24 del p.name 25 26 ### 27 xiong 28 xiong 29 deleter 30 不允许删除
1 class People: 2 def __init__(self, name): 3 self.__name = name 4 @property # 访问,查看,将一个要通过计算的属性,封装成一个用户访问就像访问一个数据属性就可以了,伪装 5 def name(self): 6 return self.__name 7 @name.setter # 修改 8 def name(self, val): 9 if not isinstance(val, str): 10 print('名字必须是字符串类型') 11 return 12 self.__name = val 13 14 @name.deleter # 删除 15 def name(self): 16 print("deleter") 17 print('不允许删除') 18 p = People('xiong') 19 print(p.name) # 访问行为 20 21 p.name = 'XIONG' # 修改行为 22 print(p.name) 23 24 del p.name 25 26 ### 27 xiong28 XIONG29 deleter 30 不允许删除
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则,不需要添加多余的括号什么的,直接访问就可以了。
10,绑定方法与非绑定方法
1,类中定义的两大类函数
1 class Foo: 2 def __init__(self, name): 3 self.name = name 4 def tell(self): # 绑定对象的方法 5 print('名字是 %s '% self.name) 6 7 @classmethod # 绑定到类的方法,绑定给谁,就应该由谁来调用,谁调用,就把谁当第一个参数传入 8 def func(cls): # cls = Foo 9 print(cls) 10 11 @staticmethod # 非绑定方法,就是一个普通的函数,谁都能用,无自动传进 12 def func1(x, y): 13 print(x + y) 14 15 f = Foo('xiong') 16 print(Foo.tell) 17 print(f.tell) 18 19 f.tell() 20 21 Foo.func() 22 print(Foo) 23 24 print(Foo.func1) # 这里证明Foo.func1和f.func1都是普通函数 25 print(f.func1) 26 print(Foo.func1(3, 4)) 27 print(f.func1(3, 4)) 28 29 ### 30 <function Foo.tell at 0x03679420> 31 <bound method Foo.tell of <__main__.Foo object at 0x036FC910>> 32 名字是 xiong 33 <class '__main__.Foo'> 34 <class '__main__.Foo'> 35 <function Foo.func1 at 0x036794B0> 36 <function Foo.func1 at 0x036794B0> 37 7 38 None 39 7 40 None
一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
1,绑定到类的方法:用classmethod装饰器装饰的方法。
为类量身定制
类.boud_method(),自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
2,绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
二:非绑定方法:用 staticmethod 装饰器装饰的方法,实际上就是一个普通函数
不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已,对象和类都可以使用
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
2,绑定方法与非绑定方法的使用
1,绑定方法的使用
1 import hashlib 2 import time 3 import settings 4 5 class People: 6 def __init__(self, name, age, sex): 7 8 self.name = name 9 self.age = age 10 self.sex = sex 11 12 def tell_info(self): # 绑定到对象的方法 13 print('Name:%s Age:%s Sex:%s' % (self.name, self.age, self.sex)) 14 15 @classmethod # 绑定到类的方法 16 def from_conf(cls): 17 obj = cls( 18 settings.name, 19 settings.age, 20 settings.sex 21 ) 22 return obj 23 24 p = People('xiong', 18, 'male') 25 26 # 绑定给对象,就应该由对象来调用,自动将对象本身当做第一个参数传入 27 # People.tell_info(p) 28 p.tell_info() 29 30 # 绑定到类,就应该有类来调用,自动将类本身当做第一个参数传入 31 p = People.from_conf() # from_conf(People) # 从配置文件里面读取配置进行实例化 32 # 等价于 p = People('xiongwu', 19, 'female') 33 34 p.tell_info() 35 36 37 # name = 'xionghao' 38 # age = 19 39 # sex = 'female' 40 41 ### 42 Name:xiong Age:18 Sex:male 43 Name:xionghao Age:19 Sex:female
2,非绑定方法的使用
1 import hashlib 2 import time 3 import settings 4 5 class People: 6 def __init__(self, name, age, gender): 7 self.name = name 8 self.age = age 9 self.gender = gender 10 11 self.id = self.create_id() 12 13 def tell_info(self): # 绑定到对象的方法 14 print('Name:%s Age:%s Gender:%s' % (self.name, self.age, self.gender)) 15 16 @classmethod 17 def from_conf(cls): 18 obj = cls( 19 settings.name, 20 settings.age, 21 settings.gender 22 ) 23 return obj 24 25 @staticmethod 26 def create_id(): 27 m = hashlib.md5() 28 m.update(str(time.time()).encode('utf-8')) 29 return m.hexdigest() 30 31 32 p = People('xiong', 18, '男') 33 p1 = People('xiongh', 19, '男') 34 p2 = People('xionghao', 20, '男') 35 36 # 非绑定方法,不与类或对象绑定,谁都可以调用,没有自动传值一说 37 print(p.id,'111') 38 print(p1.id,'2221') 39 print(p2.id,'331') 40 41 ### 42 7871d235ee8ea3e25f16178004e00eb3 111 43 7871d235ee8ea3e25f16178004e00eb3 2221 44 7871d235ee8ea3e25f16178004e00eb3 331 45 7871d235ee8ea3e25f16178004e00eb3 111 46 4e16523b5ec7f189d45d1b9cda5da01e 2221 47 4e16523b5ec7f189d45d1b9cda5da01e 331
11,反射
1,引出反射
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
以下代码可以说明没有反射不具备的功能。
1 class People: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def talk(self): 7 print('%s is talking' % self.name) 8 9 obj = People('xiong', 18) 10 11 print(obj.name) # 本质obj.__dict__['name'] # 访问数据属性,点后面是一个属性,而不是字符串 12 print(obj.talk) # 访问绑定方法 13 14 ### 15 xiong 16 <bound method People.talk of <__main__.People object at 0x0117EE70>>
注意:
不能直接调用input里面的内容,它仅仅只是一个字符串
choice = input('>>>: ') # choice = 'name' # 这个name仅仅只是一个字符串
print(obj.choice) # print(obj.'name') # 后面要是一个属性名,不能为字符串
需要解决的问题:
能够让用户通过字符串去映射到对象的一个属性身上,所以我们学习python的解决方式
2,通过字符串映射到对象
四个可以实现自省的函数 下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
1,hasattr(object,name)
第一个参数为对象,第二个参数是字符串hasattr(o, name:str)
判断object中有没有一个name字符串对应的方法或属性
2,getattr(object, name, default=None)
得到对象的属性
3,setattr(x, y, v)
修改对象的属性
4,delattr(x, y)
删除对象的属性
1 class People: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def talk(self): 7 print('%s is talking' % self.name) 8 9 obj = People('xiong', 18) 10 11 print(obj.name) # 本质obj.__dict__['name'] # 访问数据属性,点后面是一个属性,而不是字符串 12 print(obj.talk) # 访问绑定方法 13 14 15 # 查 16 print(hasattr(obj, 'name')) # obj.name # 实质 obj.__dict__ ['name'] 17 print(hasattr(obj, 'talk')) 18 19 # 取得 20 print(getattr(obj, 'name', None)) # 得到对象的属性 21 print(getattr(obj, 'talk', None)) # 得到方法的属性 22 23 # 修改 24 setattr(obj, 'sex', 'male') # 本质:obj.sex = 'male' 25 print(obj.sex) 26 27 # 删除 28 delattr(obj, 'age') # del obj.age 29 print(obj.__dict__) 30 31 ### 32 xiong 33 <bound method People.talk of <__main__.People object at 0x00F2DD30>> 34 True 35 True 36 xiong 37 <bound method People.talk of <__main__.People object at 0x00F2DD30>> 38 male 39 {'name': 'xiong', 'sex': 'male'}
3,反射的应用
1 # 反射的应用 2 class Service: 3 def run(self): 4 while True: 5 inp = input('>>>: ').strip() # get a.txt 6 cmds = inp.split() # cmds = ['get', 'a.txt'] 7 8 # print(cmds) 9 if hasattr(self, cmds[0]): 10 func = getattr(self, cmds[0]) 11 func(cmds) 12 13 def get(self, cmds): 14 print('get..............', cmds) 15 16 def put(self, cmds): 17 print('put..............', cmds) 18 19 20 obj = Service() 21 obj.run() 22 23 ###输入get a.txt或者put a.txt 24 >>>: get a.txt 25 get.............. ['get', 'a.txt'] 26 >>>: put a.txt 27 put.............. ['put', 'a.txt']
12,内置方法介绍
1,isinstance(obj,cls)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象或实例
1 # isinstance 2 class Foo(object): 3 pass 4 obj = Foo() 5 print(isinstance(obj, Foo)) 6 7 ### 8 True
2,issubclass(sub,super)
检查sub类是否是 super 类的派生类(子类是不是父类的儿子)
1 # issubclass 2 class Foo(object): 3 pass 4 class Bar(Foo): 5 pass 6 print(issubclass(Bar, Foo)) 7 8 ### 9 True
3,item系列
item系列,把对象做成像字典一样的东西,然后像字典一样去操作
1 # item系列,把对象做成像字典一样的东西,然后像字典一样去操作 2 class Foo: # 将Foo模拟成Dict系列 3 def __init__(self, name): 4 self.name = name 5 def __getitem__(self, item): # item = 'name' 6 return self.__dict__.get(item) # 这里使用self.__dict__[item],如果找不到这个key,就会报错。 7 8 def __setitem__(self, key, value): 9 print('setitem...') 10 print(key, value) 11 self.__dict__[key] = value 12 13 def __delitem__(self, key): 14 print('delitem...') 15 print(key) 16 del self.__dict__[key] 17 18 obj = Foo('xiong') 19 20 # 查看属性 21 # obj.属性名 # 一般或许对象属性的方法 22 print(obj['name']) # 完成obj.name的取值效果 23 24 # 设置属性 25 # obj.sex = 'male' 26 obj['sex'] = 'male' 27 # print(obj.__dict__) 28 # print(obj.sex) 29 30 # 删除属性 31 # del obj.name 32 del obj['name'] 33 print(obj.__dict__) 34 35 ### 36 xiong 37 setitem... 38 sex male 39 delitem... 40 name 41 {'sex': 'male'}
4,__str__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
__str__方法定义完以后,会在打印对象的时候触发对象下面的__str__方法,将返回的结果(字符串)作为打印的结果,达到返回的不是说明obj是一个对象,而是得到更多有用的信息。
1 d = dict({'name': 'xiong'}) # 本质是调dict这个类,然后将参数传进来进行实例化 2 print(isinstance(d, dict)) # dict就是d的一个对象 3 # 数据类型就是类 4 print(d) 5 6 class People(): 7 def __init__(self, name, age): 8 self.name = name 9 self.age = age 10 obj = People 11 print(obj) 12 # 上面两个print,python自带的类专门定制了打印的效果,打印出来的更加有用 13 # 为People类定制一个方法,在print对象的时候自动触发对象的一个绑定方法,来返回有用的信息。<br><br> 14 class People(): 15 def __init__(self, name, age): 16 self.name = name 17 self.age = age 18 19 def __str__(self): 20 print('===>str') 21 return '<name:%s,age:%s>' % (self.name, self.age) # 打印obj直接触发__str__,并且打印返回值 22 23 obj = People('xiong', 18) 24 print(obj) # 触发并打印 obj.__str__() 25 26 ### 27 True 28 {'name': 'xiong'} 29 <class '__main__.People'> 30 ===>str 31 <name:xiong,age:18>
5,__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
来比较下下面两个程序的运行顺序
1 # 程序1 2 class Foo: 3 4 def __del__(self): 5 print('执行我啦') 6 7 f1=Foo() 8 del f1 9 print('------->') 10 11 #输出结果 12 执行我啦 13 -------> 14 15 # 程序2 16 class Foo: 17 18 def __del__(self): 19 print('执行我啦') 20 21 f1=Foo() 22 # del f1 23 print('------->') 24 25 #输出结果 26 -------> 27 执行我啦 28 29 30 # 对比这两个程序的执行顺序,可以发现如果未执行程序del f1,则对象中的__del__是在最后执行的,这就是因为若没有手动执行del f1,方法__del__会自动帮你在程序结束的时候自动回收空间,而del f1则是自己手动执行空间的回收
典型的应用场景:
创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源
这与文件处理是一个道理:
1 # __del__ 2 f = open('settings.py') # 做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 3 del f # 只回收用户空间的f,操作系统的文件还处于打开状态 4 5 # 所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 6 f = open('settings.py') 7 # 读写... 8 f.close() # 回收操作系统的资源 9 # 很多情况下大家都容易忽略f.close,这就用到了with上下文管理 10 11 12 # 模拟打开文件操作过程 13 class Open: 14 def __init__(self, filename): 15 print('open file...') 16 self.filename = filename 17 18 def __del__(self): # 在对象被删除的时候会先自动触发这个方法的执行,再把对象删掉 19 # 这里还可以写跟资源回收相关的操作 20 print('回收操作系统的资源,类似于self.close()操作') 21 22 f = Open('settings.py') 23 # del f # 手动回收 24 print('-------end--------') # 触发了 del f # f.__del__()
13,元类
1,元类介绍
1,exec用法
1 # 储备知识 exec 2 # 参数1:字符串形式的命令 3 # 参数2:全局作用域(字典形式),如果不指定默认就使用globals() 4 # 参数3:局部作用域(字典形式),如果不指定默认就使用locals() 5 # 可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中 6 7 g = { 8 'x': 1, 9 'y': 2 10 } 11 l = {} 12 exec(""" 13 global x,m 14 x = 10 15 m = 100 16 17 z = 3 18 """, g, l) 19 print(g) 20 print(l)
2,何为元类
python中一切皆对象,对象可以怎么用?
1,都可以被引用,赋值给一个变量,x = obj
2,都可以当作函数的参数传入
3,都可以当作函数的返回值
4,都可以当作容器类的元素,l = [func, time, obj, l]
# 类也是对象
1 class Foo: 2 pass 3 obj = Foo() 4 print(type(obj)) 5 print(type(Foo))<br><br>###<br><class '__main__.Foo'><br><class 'type'>
所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化),比如对象obj是调用类Foo得到的,如果一切皆为对象,那么类Foo本质也是一个对象,既然所有的对象都是调用类得到的,那么Foo必然也是调用了一个类得到的,这个类称为元类。
产生类的类称之为元类,默认所有用class定义的类,他们的元类就是type。
元类type ===实例化===> 类Foo ===实例化===> 对象obj
2,创建类的两种方式
方式一:使用class关键字
1 # 方式一:class关键字 2 class Chinese: # Cinese = type(...) 3 country = 'China' 4 def __init__(self,name, age): 5 self.name = name 6 self.age = age 7 8 def talk(self): 9 print('%s is talking ' % self.name) 10 # print(Chinese) 11 obj = Chinese('xiong',18) 12 print(obj, obj.name, obj.age) 13 14 ### 15 <__main__.Chinese object at 0x0150C490> xiong 18
方式二:type元类产生
就是手动模拟class创建类的过程:将创建类的步骤拆分开,手动去创建
定义类的三要素
类名 class_name = 'Chinese'
基类们class_bases = (object, )
类的名称空间class_dic,类的名称空间是执行类体代码而得到的
调用type时会依次传入以上三个参数
1 #准备工作: 2 3 #创建类主要分为三部分 4 1 类名 5 2 类的父类 6 3 类体 7 8 #类名 9 class_name='Chinese' 10 11 #类的父类 12 class_bases=(object,) 13 14 #类体 15 class_body=""" 16 country='China' 17 def __init__(self,name,age): 18 self.name=name 19 self.age=age 20 def talk(self): 21 print('%s is talking' %self.name) 22 """
步骤一(先处理类体->名称空间):
类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典
class_dic={} exec(class_body,globals(),class_dic) print(class_dic) #{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}
步骤二:调用元类type(也可以自定义)来产生类Chinese1
1 hinese1=type(class_name,class_bases,class_dic) #实例化type得到对象Chinese1,即我们用class定义的类Chinese1 2 obj1 = Chinese1('xiong', 18)<br><br>print(obj1, obj1.name, obj1.age) 3 print(Chinese1) 4 print(type(Chinese1)) 5 print(isinstance(Chinese1,type)) 6 '''<br><__main__.Chinese object at 0x0150C490> xiong 18 7 <class '__main__.Chinese'> 8 <class 'type'> 9 True 10 '''
我们看到,type 接收三个参数:
- 第 1 个参数是字符串 ‘Chinese1’,表示类名
- 第 2 个参数是元组 (object, ),表示所有的父类
- 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
补充:若Chinese1类有继承,即class Chinese1(Bar):.... 则等同于type('Chinese1',(Bar,),{})
3,自定义元类控制类的行为
一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 # 知识储备: 5 # 产生的新对象 = object.__new__(继承object类的子类) 6 7 # 步骤一:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建 8 9 class Mymeta(type): # 继承默认元类的一堆属性 10 def __init__(self, class_name, class_bases, class_dic): 11 if '__doc__' not in class_dic or not class_dic.get('__doc__').strip(): 12 raise TypeError('必须为类指定文档注释') 13 14 if not class_name.istitle(): 15 raise TypeError('类名首字母必须大写') 16 17 super(Mymeta, self).__init__(class_name, class_bases, class_dic) 18 19 20 class People(object, metaclass=Mymeta): 21 country = 'China' 22 23 def __init__(self, name, age): 24 self.name = name 25 self.age = age 26 27 def talk(self): 28 print('%s is talking' % self.name) 29 30 # 步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用 31 32 class People(object,metaclass=type): 33 def __init__(self,name,age): 34 self.name=name 35 self.age=age 36 37 def __call__(self, *args, **kwargs): 38 print(self,args,kwargs) 39 40 41 # 调用类People,并不会出发__call__ 42 obj = People('egon', 18) 43 44 # 调用对象obj(1,2,3,a=1,b=2,c=3),才会出发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3) 45 obj(1, 2, 3, a=1, b=2, c=3) # 打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3} 46 47 # 总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj 48 49 # 步骤三:自定义元类,控制类的调用(即实例化)的过程 50 class Mymeta(type): # 继承默认元类的一堆属性 51 def __init__(self, class_name, class_bases, class_dic): 52 if not class_name.istitle(): 53 raise TypeError('类名首字母必须大写') 54 55 super(Mymeta, self).__init__(class_name, class_bases, class_dic) 56 57 def __call__(self, *args, **kwargs): 58 # self=People 59 print(self, args, kwargs) # <class '__main__.People'> ('egon', 18) {} 60 61 # 1、实例化People,产生空对象obj 62 obj=object.__new__(self) 63 64 65 # 2、调用People下的函数__init__,初始化obj 66 self.__init__(obj, *args, **kwargs) 67 68 69 # 3、返回初始化好了的obj 70 return obj 71 72 class People(object,metaclass=Mymeta): 73 country='China' 74 75 def __init__(self,name,age): 76 self.name=name 77 self.age=age 78 79 def talk(self): 80 print('%s is talking' %self.name) 81 82 obj=People('egon',18) 83 print(obj.__dict__) # {'name': 'egon', 'age': 18} 84 85 # 步骤四: 86 class Mymeta(type): # 继承默认元类的一堆属性 87 def __init__(self, class_name, class_bases, class_dic): 88 if not class_name.istitle(): 89 raise TypeError('类名首字母必须大写') 90 91 super(Mymeta, self).__init__(class_name, class_bases, class_dic) 92 93 def __call__(self, *args, **kwargs): 94 # self=People 95 print(self, args, kwargs) # <class '__main__.People'> ('egon', 18) {} 96 97 # 1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj 98 obj = self.__new__(self, *args, **kwargs) 99 100 # 2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值 101 return obj 102 103 class People(object,metaclass=Mymeta): 104 country = 'China' 105 106 def __init__(self, name, age): 107 self.name = name 108 self.age = age 109 110 def talk(self): 111 print('%s is talking' % self.name) 112 113 def __new__(cls, *args, **kwargs): 114 obj=object.__new__(cls) 115 cls.__init__(obj, *args, **kwargs) 116 return obj 117 118 obj=People('egon',18) 119 print(obj.__dict__) # {'name': 'egon', 'age': 18} 120 121 # 步骤五:基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存 122 class Mysql: 123 __instance = None 124 def __init__(self,host='127.0.0.1',port='3306'): 125 self.host=host 126 self.port=port 127 128 @classmethod 129 def singleton(cls,*args,**kwargs): 130 if not cls.__instance: 131 cls.__instance=cls(*args,**kwargs) 132 return cls.__instance 133 134 135 obj1 = Mysql() 136 obj2 = Mysql() 137 print(obj1 is obj2) # False 138 139 obj3 = Mysql.singleton() 140 obj4 = Mysql.singleton() 141 print(obj3 is obj4) # True 142 143 # 应用:定制元类实现单例模式 144 class Mymeta(type): 145 def __init__(self, name, bases, dic): # 定义类Mysql时就触发 146 self.__instance = None 147 super().__init__(name, bases, dic) 148 149 def __call__(self, *args, **kwargs): # Mysql(...)时触发 150 151 if not self.__instance: 152 self.__instance = object.__new__(self) # 产生对象 153 self.__init__(self.__instance, *args, **kwargs) # 初始化对象 154 # 上述两步可以合成下面一步 155 # self.__instance=super().__call__(*args,**kwargs) 156 157 return self.__instance 158 class Mysql(metaclass=Mymeta): 159 def __init__(self, host='127.0.0.1', port='3306'): 160 self.host = host 161 self.port = port 162 163 164 obj1 = Mysql() 165 obj2 = Mysql() 166 167 print(obj1 is obj2)
14,面向对象的软件开发
15,异常处理
1,何为异常
异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下
错误分为两种:
1,语法错误:这种错误过不了python解释器的语法检测,在程序执行前就要改正过来
1 #语法错误示范一 2 if 3 #语法错误示范二 4 def test: 5 pass 6 #语法错误示范三 7 class Foo 8 pass 9 #语法错误示范四 10 print(haha)
2,逻辑错误:
1 #TypeError:int类型不可迭代 2 for i in 3: 3 pass 4 #ValueError 5 num=input(">>: ") #输入hello 6 int(num) 7 8 #NameError 9 aaa 10 11 #IndexError 12 l=['xiong','aa'] 13 l[3] 14 15 #KeyError 16 dic={'name':'huihui'} 17 dic['age'] 18 19 #AttributeError 20 class Foo:pass 21 Foo.x 22 23 #ZeroDivisionError:无法完成计算 24 res1=1/0 25 res2=1+'str'
2,异常的种类
在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类)去标识,一个异常标识一种错误
常见异常
1 AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x 2 IOError 输入/输出异常;基本上是无法打开文件 3 ImportError 无法引入模块或包;基本上是路径问题或名称错误 4 IndentationError 语法错误(的子类) ;代码没有正确对齐 5 IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5] 6 KeyError 试图访问字典里不存在的键 7 KeyboardInterrupt Ctrl+C被按下 8 NameError 使用一个还未被赋予对象的变量 9 SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了) 10 TypeError 传入对象类型与要求的不符合 11 UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量, 12 导致你以为正在访问它 13 ValueError 传入一个调用者不期望的值,即使值的类型是正确的
1 ArithmeticError 2 AssertionError 3 AttributeError 4 BaseException 5 BufferError 6 BytesWarning 7 DeprecationWarning 8 EnvironmentError 9 EOFError 10 Exception 11 FloatingPointError 12 FutureWarning 13 GeneratorExit 14 ImportError 15 ImportWarning 16 IndentationError 17 IndexError 18 IOError 19 KeyboardInterrupt 20 KeyError 21 LookupError 22 MemoryError 23 NameError 24 NotImplementedError 25 OSError 26 OverflowError 27 PendingDeprecationWarning 28 ReferenceError 29 RuntimeError 30 RuntimeWarning 31 StandardError 32 StopIteration 33 SyntaxError 34 SyntaxWarning 35 SystemError 36 SystemExit 37 TabError 38 TypeError 39 UnboundLocalError 40 UnicodeDecodeError 41 UnicodeEncodeError 42 UnicodeError 43 UnicodeTranslateError 44 UnicodeWarning 45 UserWarning 46 ValueError 47 Warning 48 ZeroDivisionError 49 50 更多异常
3,异常处理
为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,我们需要对异常进行处理,
1,如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防
1 AGE=10 2 while True: 3 age=input('>>: ').strip() 4 if age.isdigit(): #只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的 5 age=int(age) 6 if age == AGE: 7 print('you got it') 8 break
2,如果错误发生的条件是不可预知的,则需要用到try...except:在错误发生之后进行处理
1 #基本语法为 2 try: 3 被检测的代码块 4 except 异常类型: 5 try中一旦检测到异常,就执行这个位置的逻辑 6 #举例 7 try: 8 f=open('a.txt') 9 g=(line.strip() for line in f) 10 print(next(g)) 11 print(next(g)) 12 print(next(g)) 13 print(next(g)) 14 print(next(g)) 15 except StopIteration: 16 f.close()
4,try...except...详细用法
1.异常类只能用来处理指定的异常情况,如果非指定异常则无法处理
1 s1 = 'hello' 2 try: 3 int(s1) 4 except IndexError as e: # 未捕获到异常,程序直接报错 5 print e
2.多分支,被检测的代码块抛出的异常有多种可能性,并且我们需要针对每一种都定制专门的处理逻辑
1 s1 = 'hello' 2 try: 3 int(s1) 4 except IndexError as e: 5 print(e) 6 except KeyError as e: 7 print(e) 8 except ValueError as e: 9 print(e)
3.万能异常Exception,被检测的代码块抛出的异常有多种可能性,并且我们针对所有的异常类型都只用一种处理逻辑就可以了。
1 s1 = 'hello' 2 try: 3 int(s1) 4 except Exception as e: 5 print(e)
4.也可以在多分支后来一个Exception
1 s1 = 'hello' 2 try: 3 int(s1) 4 except IndexError as e: 5 print(e) 6 except KeyError as e: 7 print(e) 8 except ValueError as e: 9 print(e) 10 except Exception as e: 11 print(e)
5.异常的其他结构:finally,回收机制使用
1 s1 = 'hello' 2 try: 3 int(s1) 4 except IndexError as e: 5 print(e) 6 except KeyError as e: 7 print(e) 8 except ValueError as e: 9 print(e) 10 #except Exception as e: 11 # print(e) 12 else: 13 print('try内代码块没有异常则执行我') 14 finally: 15 print('无论异常与否,都会执行该模块,通常是进行清理工作')
6.主动触发异常:raise 异常类型(值)
try: raise TypeError('类型错误') except Exception as e: print(e) #或者下面程序你不想让用户传入数字当作名字 class People: def __init__(self, name, age): if not isinstance(name, str): raise TypeError('名字必须传入str类型') if not isinstance(age, int): raise TypeError('年龄必须传入int类型') self.name = name self.age = age p = People(2222,18) ### 这时如果用户输入数字当做用户名,就会直接报错了
7.自定义异常
1 class MynException(BaseException): 2 def __init__(self,msg): 3 self.msg=msg 4 def __str__(self): 5 return self.msg 6 7 try: 8 raise MyException('类型错误') 9 except MyException as e: 10 print(e)
8.断言:assert 条件
1 info = {} 2 info['name'] = 'xiong' 3 info['age'] = 18 4 assert('name' in info) and ('age' in info) # 判断上面是否name and age in info.否则报错
9.总结try..except
1:把错误处理和真正的工作分开来
2:代码更易组织,更清晰,复杂的工作任务更容易实现;
3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了