十一.python面向对象(进阶)
1.反射
1.1 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
1.2 反射的四个方法
1.hasattr(object,name)
判断object中有没有一个name字符串对应的方法或属性
2.getattr(object, name, default=None)
获取属性,获取object中是否有name属性,如果有则获取,如果没有则报错,如果设置default则返回default的值,不会报错。
3.setattr(object, k, v)
设置属性,设置object的属性,k为设置属性的键,v为设置属性的值。
4.delattr(object, x)
删除属性,删除object的x属性。
方法示例:
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 class House: 5 def __init__(self,length,width): 6 self.length = length 7 self.width = width 8 9 def Area(self): 10 return "房子的面积为%s平方米" %(self.length * self.width) 11 12 13 h1 = House(10,20) 14 #打印h1的属性字典 15 print(h1.__dict__) 16 #判断是否有length属性(返回bool值) 17 print(hasattr(h1,'length')) #切记length属性是字符串类型****** 18 #获取length属性(返回获取到的值,default默认是不存在,获取不到返回"不存在") 19 print(getattr(h1,'lengtha','不存在')) 20 #设置属性 21 print(setattr(h1,'height','30')) 22 #设置完查看 23 print(h1.__dict__) 24 #删除属性(删除不存在的会报错,AttributeError) 25 delattr(h1,'length') 26 #删除完查看 27 print(h1.__dict__)
结果:
1 {'length': 10, 'width': 20} 2 True 3 不存在 4 None 5 {'length': 10, 'width': 20, 'height': '30'} 6 {'width': 20, 'height': '30'}
1.3 为什么用反射
可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。
实现可插拔机制
示例:
A程序员
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 #A程序员写文件的方法,有读写操作,但目前只写了读的方法。 5 class File: 6 def read(self): 7 print("读操作") 8 9 # def write(self): 10 # print("写操作")
B程序员(B程序员不用关心A程序员是否写write方法,利用反射判断属性并执行,效率高)
#!/usr/bin/env python # -*- coding: utf-8 -*- from test import File f1 = File() if hasattr(f1,'write'): f1 = hasattr(f1,'write') f1() else: print("属性不存在继续写逻辑")
2.类的特殊方法
2.1 对象的属性操作
__setattr__ 添加/修改属性会触发它的执行
1 # __setattr__ 添加/修改属性会触发它的执行 2 class Foo: 3 x=1 4 def __init__(self,y): 5 self.y=y 6 7 def __setattr__(self, key, value): 8 print('----> from setattr') 9 #以下模拟了给对象添加属性的原理,重写了对象添加属性。 10 # self.key=value #模拟添加属性,这样做会导致递归。 11 self.__dict__[key]=value #这样做不会递归 12 13 f1=Foo(10) #实例化等于添加self.y属性 14 print(f1.__dict__) #查看其实并未添加,因为当前的__setattr__覆盖了系统的,以上加了self.__dict__[key]=value,才进行了属性的添加 15 f1.x = 20 #为对象手动添加属性 16 print(f1.__dict__)
结果:
1 ----> from setattr 2 {'y': 10} 3 ----> from setattr 4 {'x': 20, 'y': 10}
__delattr__ 删除属性的时候会触发
1 # __delattr__ 删除属性的时候会触发 2 3 class Foo: 4 x=1 5 def __init__(self,y): 6 self.y=y 7 8 def __delattr__(self, item): 9 print('----> from delattr') 10 print('----> %s' %item) #item为删除的值 11 # del self.item #这样做会无限递归 12 self.__dict__.pop(item) #操作属性字典删除 13 14 f1 = Foo(10) 15 print(f1.__dict__) 16 del f1.y #删除属性y 17 print(f1.__dict__)
结果:
1 {'y': 10} 2 ----> from delattr 3 ----> y 4 {}
__getattr__ 只有在使用点调用属性且属性不存在的时候才会触发
1 # __getattr__ 只有在使用点调用属性且属性不存在的时候才会触发 2 3 class Foo: 4 x=1 5 def __init__(self,y): 6 self.y=y 7 8 def __getattr__(self, item): 9 print('----> from getattr:你找的属性不存在') 10 11 f1 = Foo(10) 12 13 print(f1.y) #属性存在的时候不会触发 14 print(f1.z) #属性不存在的时候会触发
结果:
1 10 2 ----> from getattr:你找的属性不存在 3 None
__getattribute__ 不管属性是否存在都会调用
1 # __getattribute__ 在使用点调用属性就会执行 2 3 class Foo: 4 def __init__(self,x): 5 self.x=x 6 7 def __getattribute__(self, item): 8 print('from------>__getattribute__') 9 10 f1=Foo(10) 11 #__getattribute__与__getattr__不同的是,不管属性是否存在都会调用。 12 f1.x 13 f1.yyy
结果:
1 from------>__getattribute__ 2 from------>__getattribute__
__getattribute__ 与__getattr__ 同时出现
1 # __getattr__与__getattribute__共存 2 3 class Foo: 4 def __init__(self,x): 5 self.x=x 6 7 def __getattr__(self, item): 8 print('from------>__getattr__') 9 10 def __getattribute__(self, item): 11 print('from ------>__getattribute__') 12 #raise AttributeError('没有这个属性') 13 14 f1=Foo(10) 15 f1.x #共存调用有的x属性执行__getattribute__ 16 f1.yyy #共存调用没有的yyy属性也执行__getattribute__ 17 18 #总结:当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError
__getitem__:obj['属性'] 时触发
1 # __getitem__:obj['属性'] 时触发 2 3 class Foo: 4 x=1 5 def __init__(self,y): 6 self.y=y 7 8 def __getitem__(self, item): 9 print('from ----->__getitem__') 10 print(self.__dict__[item]) 11 12 f1 = Foo(10) 13 14 print(f1['y'])
结果:
1 from ----->__getitem__ 2 10 3 None
__setitem__:obj['属性']=属性的值 时触发
1 # __setitem__:obj['属性']=属性的值 时触发 2 class Foo: 3 x=1 4 def __init__(self,y): 5 self.y=y 6 7 def __setitem__(self, key, value): 8 print('from ------>__setitem__') 9 self.__dict__[key] = value 10 11 f1=Foo(10) 12 print(f1.__dict__) 13 f1['z'] = 30 14 print(f1.__dict__)
结果:
1 {'y': 10} 2 from ------>__setitem__ 3 {'z': 30, 'y': 10}
__delitem__:del obj['属性'] 时触发
1 # __delitem__:del obj['属性'] 时触发 2 class Foo: 3 x=1 4 def __init__(self,y): 5 self.y=y 6 7 def __delitem__(self, key): 8 print('from ------>__delitem__') 9 self.__dict__.pop(key) 10 11 f1=Foo(10) 12 print(f1.__dict__) 13 del f1['y'] 14 print(f1.__dict__)
结果:
1 {'y': 10}
2 from ------>__delitem__
3 {}
2.2 检查对象和子类
isinstance语法:isinstance(obj,cls)
功能:检查对象obj是否是类cls的实例
1 class Foo(object): 2 pass 3 4 obj = Foo() 5 print(isinstance(obj, Foo)) #判断obj是否是Foo的实例
issubclass语法:issubclass(sub,super)
功能:检查sub类是否是super类的派生类
1 class Foo(object): 2 pass 3 4 class Bar(Foo): 5 pass 6 7 print(issubclass(Bar,Foo)) #判断Bar是否是Foo的子类
2.3 __str__与__repr__
__str__与__repr__是控制输出的,__str__是控制print输出的,__repr__是控制控制台输出的
str函数或者print函数--->obj.__str__()
repr或者交互式解释器--->obj.__repr__()
注意:必须以return结尾,并且return的值只能是字符串
1 class Foo: 2 def __init__(self,name,age): 3 self.name = name 4 self.age = age 5 #自定义str 6 def __str__(self): 7 return "name is %s,age is %s" %(self.name,self.age) 8 9 f1 = Foo('zhangsan',23) 10 11 print(f1) #str(f1)--->f1.__str__() #print调用顺序 12 # 这里不定义__str__会显示<__main__.Foo object at 0x0000025B3895B668> 13 # 定制str会显示 14 # name is zhangsan,age is 23
1 class Foo: 2 def __init__(self,name,age): 3 self.name = name 4 self.age = age 5 #自定义repr 6 def __repr__(self): 7 return "name is %s,age is %s" %(self.name,self.age) 8 9 f1 = Foo('zhangsan',23) 10 11 print(f1) #repr(f1)--->f1.__repr__() #调用顺序 12 13 ###############控制台示例##################### 14 >>> 15 >>> class Foo: 16 ... def __init__(self,name,age): 17 ... self.name = name 18 ... self.age = age 19 ... def __repr__(self): 20 ... return "name is %s,age is %s" %(self.name,self.age) 21 ... 22 >>> f1 = Foo('zhangsan',23) 23 >>> f1 24 name is zhangsan,age is 23 25 >>>
1 class Foo: 2 def __init__(self,name,age): 3 self.name = name 4 self.age = age 5 6 def __str__(self): 7 return ("from---->str") 8 9 def __repr__(self): 10 return "name is %s,age is %s" %(self.name,self.age) 11 12 f1 = Foo('zhangsan',23) 13 14 print(f1) # 共存调用顺序,str(f1)--->f1.__str__()--->f1.__repr__()
2.4自定制format
1 format_dic = { 2 'ymd':'{0.year} {0.month} {0.day}', 3 'y-m-d':'{0.year}-{0.month}-{0.day}', 4 'm:d:y':'{0.month}:{0.day}:{0.year}', 5 'd/m/y':'{0.day}/{0.month}/{0.year}', 6 } 7 8 class Date: 9 def __init__(self,year,month,day): 10 self.year = year 11 self.month = month 12 self.day = day 13 14 def __format__(self, format_spec): 15 if not format_spec or format_spec not in format_dic: 16 format_spec = 'ymd' 17 fm = format_dic[format_spec] 18 return fm.format(self) 19 # return format_dic[format_spec].format(self) #整合 20 21 22 d1 = Date('2017','07','07') 23 x = format(d1,'ymd') 24 y = format(d1,'y-m-d') 25 z = format(d1,'d/m/y') 26 print(x) 27 print(y) 28 print(z) 29 30 ##############结果############## 31 2017 07 07 32 2017-07-07 33 07/07/2017
2.5__slots__
什么是__slots__?
定义:__slots__是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
为什么使用__slots__?
- 前面学过不管是访问类的属性还是实例的属性其实都是对属性字典__dict__来做操作,但是类的属性字典是共享的,实例的属性字典是独立的;如果一个类属性很少,会占用一个字典,而这个类生成的多个实例会生成多个字典,字典一多,就会占用大量的内存,为了节省内存,可以使用__slots__来代替__dict__,这样所有实例的属性字典就会是一个__slots__,减少了内存的占用。
使用__slots__的优缺点。
- 优点:节省内存空间。
- 缺点:不能给实例添加新的属性,如果添加,只能在定义__slots__里面定义。
__slots__应该用在哪?
- __slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
__slots__使用误区
- 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。
1 class Foo: 2 __slots__ = 'name' #定义单个属性,没有定义后面就不能添加。 3 # __slots__ = ['name','age'] #定义多个属性,没有定义后面就不能添加。 4 5 f1 = Foo() 6 f1.name = 'zhangsan' 7 #print(f1.__dict__) #当使用__slots__的时候,就没有属性字典了,这里会报错,(AttributeError: 'Foo' object has no attribute '__dict__') 8 print(f1.__slots__) # name 9 #f1.age = 23 #如果__slots__没有这个属性,这里会报错(AttributeError: 'Foo' object has no attribute 'age') 10 11 12 f2 = Foo() 13 f2.name = 'lisi' 14 print(f2.__dict__) #当使用__slots__的时候,就没有属性字典了,这里会报错,(AttributeError: 'Foo' object has no attribute '__dict__') 15 print(f2.__slots__) #name 16 17 #总结:f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
2.6__doc__
__doc__就是文档描述符,用来描述类或方法的作用,可以任意定义,自己能看懂就好。
特点:无法继承
1 class Foo: 2 '我是描述信息' 3 pass 4 5 class Bar(Foo): 6 pass 7 print(Bar.__doc__) #该属性无法继承给子类
2.7__module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
创建test/test.py
1 class Foo: 2 def __init__(self,name): 3 self.name = name
导入test.py模块
1 from test.test import Foo 2 3 obj = Foo() 4 print obj.__module__ #输出 test.test,即:输出模块 5 print obj.__class__ #输出 test.test.Foo,即:输出类
2.8__del__ 析构方法
- 定义:析构方法,当对象在内存中被释放时,自动触发执行。
1 class Foo: 2 def __init__(self,name): 3 self.name = name 4 5 def __del__(self): 6 print("from ------>__del__") 7 8 f1 = Foo('zhangsan') 9 print('--------------程序执行完------------>') 10 11 12 #结果 13 --------------程序执行完------------> 14 from ------>__del__ 15 16 #结果可以看出程序执行完毕,触发析构函数。
1 class Foo: 2 def __init__(self,name): 3 self.name = name 4 5 def __del__(self): 6 print("from ------>__del__") 7 8 f1 = Foo('zhangsan') 9 del f1 10 print('--------------程序执行完------------>') 11 12 #结果 13 from ------>__del__ 14 --------------程序执行完------------> 15 16 #以上可以看出,删除f1实例,触发析构函数,然后程序执行完毕。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
2.9 __call__
对象后面加括号,触发执行。
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 class Foo: 5 def __init__(self,name): 6 self.name = name 7 8 def __call__(self, *args, **kwargs): 9 print('from------>__call__') 10 11 f1 = Foo('zhangsan') 12 13 14 f1() #执行Foo下的__call__方法 15 Foo('zhangsan')() #等同于f1(),执行Foo下的__call__方法 16 Foo('zhangsan') #例如Foo()触发执行的是abc下的__call__方法。 17 18 结果# 19 from------>__call__ 20 from------>__call__
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
2.10 __next__和__iter__实现迭代器协议
1 class Foo: 2 def __init__(self,x): 3 self.x=x 4 5 def __iter__(self): 6 return self 7 8 def __next__(self): 9 n=self.x 10 self.x+=1 11 return self.x 12 13 f=Foo(3) 14 for i in f: 15 print(i) 16 17 #1.首先要有__iter__,生成迭代器对象的时候会调用 18 #2.要有__next__方法,因为可以被next()或__next__()函数调用并不断返回下一个值(直到没有数据时抛出StopIteration错误)的对象称为迭代器 19 #3.强大的for循环遇到StopIteration会自动停止,并且不会报错。
1 class Foo: 2 def __init__(self): 3 self.x = 1 4 self.y = 1 5 6 def __iter__(self): 7 return self 8 9 def __next__(self): 10 self.x,self.y = self.y,self.x + self.y 11 if self.x > 20: 12 raise StopIteration('异常了。。。') 13 return self.x 14 15 f1 = Foo() 16 17 # print(f1.__next__()) 18 # print(f1.__next__()) 19 # print(f1.__next__()) 20 # print(f1.__next__()) 21 # print(f1.__next__()) 22 # print(f1.__next__()) 23 # print(f1.__next__()) 24 # print(f1.__next__()) 25 26 for i in f1: 27 print(i)
3.描述符
1.描述符是什么?
- 描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议;python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。如果__get__(),__set__(),__delete__()被定义在一个对象中,这个对象就是一个描述符。
- 定义描述符
1 class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符(实现任意一个都被称作描述符) 2 def __get__(self, instance, owner): 3 pass 4 def __set__(self, instance, value): 5 pass 6 def __delete__(self, instance): 7 pass
2.描述符是做什么的?
- 描述符的作用是用来代理另外一个类的属性的(注意:必须把描述符定义成这个类的类属性,不能定义到构造函数中)
为什么说是代理属性呢?
1 class Foo: 2 def __get__(self, instance, owner): 3 print('触发get') 4 def __set__(self, instance, value): 5 print('触发set') 6 def __delete__(self, instance): 7 print('触发delete') 8 9 #包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法 10 f1=Foo() 11 f1.name='zhangsan' 12 f1.name 13 del f1.name
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用...') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 #描述符Int 11 class Int: 12 def __get__(self, instance, owner): 13 print('Int调用...') 14 def __set__(self, instance, value): 15 print('Int设置...') 16 def __delete__(self, instance): 17 print('Int删除...') 18 19 class People: 20 name=Str() #Str描述符用在这里 21 age=Int() #Int描述符用在这里 22 def __init__(self,name,age): #name被Str类代理,age被Int类代理 23 self.name=name 24 self.age=age 25 26 #实例化的时候会调用,zhangsan--->name(因为name被Str代理)--->Str--->触发Str下的__set__方法(因为实例化本身就是把zhangsan赋值给name,是设置操作) 27 #age和name相同只不过是被Int代理。 28 p1=People('zhangsan',23) 29 #被代理的属性(name和age,在示例本身属性字典里查不到) 30 print(p1.__dict__) 31 #实例name属性被Str代理,调用/赋值/删除会触发Str描述符的方法 32 print('------>描述符Str的使用') 33 p1.name 34 p1.name='zhangsan' 35 #因为name被代理,所以代理里面没有操作,属性字典为空 36 print(p1.__dict__) 37 del p1.name 38 #实例age属性被Int代理,调用/赋值/删除会触发Int描述符的方法 39 print('------>描述符Int的使用') 40 p1.age 41 p1.age=23 42 #因为name被代理,所以代理里面没有操作,属性字典为空 43 print(p1.__dict__) 44 del p1.age 45 print(type(p1))
1 Str设置... 2 Int设置... 3 {} 4 ------>描述符Str的使用 5 Str调用... 6 Str设置... 7 {} 8 Str删除... 9 ------>描述符Int的使用 10 Int调用... 11 Int设置... 12 {} 13 Int删除...
总结:描述符就是来代理其他的类的属性操作的。
3.描述符的种类
描述符分为两种
1.数据描述符:至少实现了__get__()和__set__()
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get')
2.非数据描述符:没有实现__set__()
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get')
4.使用描述符要注意的事项*****
1.描述符本身应该定义成新式类,被代理的类也应该是新式类***
2.必须把描述符定义成这个类的类属性,不能为定义到构造函数中(__init__())***
3. 要严格遵循该优先级,优先级由高到底分别是
1) 类属性
2) 数据描述符
3) 实例属性
4) 非数据描述符
5) 找不到的属性触发__getattr__()
1 #描述符Str,因为至少包含了__get__和__set__,所以具体可以称为数据描述符。 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用...') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str类代理,age被Int类代理 13 self.name=name 14 self.age=age 15 16 #基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典 17 #那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,这里做下实验。 18 People.name #测试通过,可以调用。但是本质就是在调用描述符Str,触发了__get__(),但并不能证明类(属性>数据描述符) 19 #再来看赋值和删除 20 People.name = 'zhangsan' #没有触发__set__方法 21 del People.name #没有触发__delete__方法 22 23 ''' 24 结论:从类对属性的赋值和删除的时候,并没有触发__set__和__delete__方法,说明类对属性的操作要高于描述符。 25 ''' 26 27 类属性>数据描述符
1 #描述符Str,因为至少包含了__get__和__set__,所以具体可以称为数据描述符。 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用...') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str类代理 13 self.name=name 14 self.age=age 15 16 #这里定义一个实例 17 p1=People('zhangsan',23) 18 19 #这里用实例对属性的调用/赋值/删除来判断实例属性和数据描述符的优先级 20 print("开始测试......") 21 p1.name 22 p1.name='lisi' 23 del p1.name 24 ''' 25 结果: 26 Str设置... 27 开始测试...... 28 Str调用... 29 Str设置... 30 Str删除... 31 32 结论:数据描述符>实例属性 33 '''
1 #描述符Str,因没有__set__方法,所以具体可以称为非数据描述符。 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用...') 5 6 class People: 7 name=Str() 8 def __init__(self,name,age): #name被Str类代理 9 self.name=name 10 self.age=age 11 12 #这里定义一个实例 13 p1=People('zhangsan',23) 14 15 #这里用调用示例属性的方式来判断实例属性和非数据描述符的优先级 16 p1.name #并没有触发Str的__get__方法。 17 print(p1.name) #可以找到,说明调用的是实例的属性字典。 18 19 ''' 20 结果:zhangsan 21 结论:实例属性>非数据描述符 22 '''
5.描述符的使用
python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
1 class Typed: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 6 def __get__(self, instance, owner): 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 if not isinstance(value,self.expected_type): 13 raise TypeError('当前类型是:%s 需要改为:%s' %(type(value),str(self.expected_type))) 14 instance.__dict__[self.name]=value 15 16 def __delete__(self, instance): 17 instance.__dict__.pop(self.name) 18 19 class People: 20 name=Typed('name',str) 21 age=Typed('age',int) 22 salary=Typed('name',float) 23 def __init__(self,name,age,salary): 24 self.name=name 25 self.age=age 26 self.salary=salary 27 28 #p1=People(123,18,3333.3) 29 #p1=People('zhangsan','18',3333.3) 30 #p1=People('zhangsan',18,3333)
以上Typed属于调用重复的代码,这里可以用类的装饰器实现
类的装饰器分类:
1 def decorate(cls): 2 print('类的装饰器开始运行啦------>') 3 return cls 4 5 @decorate #无参:People=decorate(People) 6 class People: 7 def __init__(self,name,age,salary): 8 self.name=name 9 self.age=age 10 self.salary=salary 11 12 p1=People('zhangsan',23,3333.3)
1 def typeassert(**kwargs): 2 def decorate(cls): 3 print('类的装饰器开始运行啦------>',kwargs) 4 return cls 5 return decorate 6 @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) 7 class People: 8 def __init__(self,name,age,salary): 9 self.name=name 10 self.age=age 11 self.salary=salary 12 13 p1=People('zhangsan',23,3333.3)
1 class Typed: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 print('set--->',instance,value) 13 if not isinstance(value,self.expected_type): 14 raise TypeError('Expected %s' %str(self.expected_type)) 15 instance.__dict__[self.name]=value 16 def __delete__(self, instance): 17 print('delete--->',instance) 18 instance.__dict__.pop(self.name) 19 20 def typeassert(**kwargs): 21 def decorate(cls): 22 print('类的装饰器开始运行啦------>',kwargs) 23 for name,expected_type in kwargs.items(): 24 setattr(cls,name,Typed(name,expected_type)) 25 return cls 26 return decorate 27 @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) 28 class People: 29 def __init__(self,name,age,salary): 30 self.name=name 31 self.age=age 32 self.salary=salary 33 34 print(People.__dict__) 35 p1=People('zhangsan',23,3333.3)
6.描述符实战
6.1 自制@property
1 class Lazyproperty: 2 def __init__(self,func): 3 self.func=func 4 def __get__(self, instance, owner): 5 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 6 if instance is None: 7 return self 8 return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情 9 10 class Room: 11 def __init__(self,name,width,length): 12 self.name=name 13 self.width=width 14 self.length=length 15 16 @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 17 def area(self): 18 return self.width * self.length 19 20 r1=Room('alex',1,1) 21 print(r1.area)
1 class Lazyproperty: 2 def __init__(self,func): 3 self.func=func 4 def __get__(self, instance, owner): 5 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 6 if instance is None: 7 return self 8 else: 9 print('--->') 10 value=self.func(instance) 11 setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中 12 return value 13 14 class Room: 15 def __init__(self,name,width,length): 16 self.name=name 17 self.width=width 18 self.length=length 19 20 @Lazyproperty #area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符' 21 def area(self): 22 return self.width * self.length 23 24 r1=Room('alex',1,1) 25 print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法 26 print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
6.2 自制@classmethod
1 class ClassMethod: 2 def __init__(self,func): 3 self.func=func 4 5 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 6 def feedback(): 7 print('在这里添加功能...') 8 return self.func(owner) 9 return feedback 10 11 class People: 12 name='lisi' 13 @ClassMethod # say_hi=ClassMethod(say_hi) 14 def say_hi(cls): 15 print('你好 %s' %cls.name) 16 17 People.say_hi() 18 19 p1=People() 20 p1.say_hi() 21 #疑问,类方法如果有参数呢 22 23 class ClassMethod: 24 def __init__(self,func): 25 self.func=func 26 27 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 28 def feedback(*args,**kwargs): 29 print('在这里添加功能...') 30 return self.func(owner,*args,**kwargs) 31 return feedback 32 33 class People: 34 name='lisi' 35 @ClassMethod # say_hi=ClassMethod(say_hi) 36 def say_hi(cls,msg): 37 print('你好 %s %s' %(cls.name,msg)) 38 39 People.say_hi('见到你很高兴') 40 41 p1=People() 42 p1.say_hi('见到你很高兴')
6.3 自制@staticmethod
1 class StaticMethod: 2 def __init__(self,func): 3 self.func=func 4 5 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 6 def feedback(*args,**kwargs): 7 print('在这里可以加功能啊...') 8 return self.func(*args,**kwargs) 9 return feedback 10 11 class People: 12 @StaticMethod# say_hi=StaticMethod(say_hi) 13 def say_hi(x,y,z): 14 print('------>',x,y,z) 15 16 People.say_hi(1,2,3) 17 18 p1=People() 19 p1.say_hi(4,5,6)
7.描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性
描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
4.上下文管理器
4.1 什么是上下文管理器?
在使用Python编程中,可以会经常碰到这种情况:有一个特殊的语句块,在执行这个语句块之前需要先执行一些准备动作;当语句块执行完成后,需要继续执行一些收尾动作。这就是上下文管理器。
4.2 为什么要使用?用在哪?
示例1:
当需要操作文件或数据库的时候,首先需要获取文件句柄或者数据库连接对象,当执行完相应的操作后,需要执行释放文件句柄或者关闭数据库连接的动作。
示例2:
当多线程程序需要访问临界资源的时候,线程首先需要获取互斥锁,当执行完成并准备退出临界区的时候,需要释放互斥锁。
对于这些情况,Python中提供了上下文管理器(Context Manager)的概念,可以通过上下文管理器来定义/控制代码块执行前的准备动作,以及执行后的收尾动作。
4.3 上下文管理协议又是什么?
当我们需要创建一个上下文管理器类型的时候
必须要实现:
__enter__方法(上文)
__exit__方法(下文)
这对方法就称为上下文管理协议(Context Manager Protocol),定义了一种运行时上下文环境。
4.4 如何使用上下文管理?
这里利用文件操作with open 来理解上下文管理的用法。
我们在操作文件对象的时候可以这可写,如下
1 with open('a.txt') as f: 2 '代码块'
这里的with open('a.txt')代表的意思是打开一个文件句柄,as f的意思是赋值给f,这个过程就是上下文管理协议。
这里来模拟with open的实现,来理解如何使用。
1 ''' 2 with obj as f 做的是f = obj.__enter__(),with obj 是触发obj.__enter__拿到返回值,as f是赋值给f 3 执行代码块: 4 1.没有异常的情况,整个代码块执行完成后触发__exit__方法,它的三个参数都为None 5 2.有异常的情况,会从异常出现的位置直接触发__exit__的运行、 6 a:如果__exit__的返回值为True,代表吞掉异常 7 b:如果__exit__的返回值不为True,代表吐出异常 8 c:__exit__的运行完毕就代表了整个with语句的执行完毕。 9 10 ''' 11 #没有异常 12 class Open1: 13 14 def __init__(self,name): 15 self.name = name 16 17 def __enter__(self): 18 print("执行enter") 19 return self 20 21 def __exit__(self, exc_type, exc_val, exc_tb): 22 print("执行exit") 23 print("参数开始------------------>") 24 print(exc_type) 25 print(exc_val) 26 print(exc_tb) 27 print("参数结束------------------>") 28 29 with Open1('a.txt') as f: 30 pass 31 32 print('='*50) 33 34 #有异常 35 class Open: 36 37 def __init__(self,name): 38 self.name = name 39 40 def __enter__(self): 41 print("执行enter") 42 return self 43 44 def __exit__(self, exc_type, exc_val, exc_tb): 45 print("执行exit") 46 print("参数开始------------------>") 47 print(exc_type) 48 print(exc_val) 49 print(exc_tb) 50 print("参数结束------------------>") 51 #return True #这里代表吞掉异常。开启就会吞掉异常 52 53 with Open('a.txt') as f: 54 raise AttributeError("出错了。。。") 55 print('执行结束。。。。。。。。。。。。。') #如果吞掉异常,这里会执行,如果吐出异常这里不会执行 56 57 模拟with open的实现过程
1 class Open: 2 def __init__(self,filepath,mode='r',encoding='utf-8'): 3 self.filepath=filepath 4 self.mode=mode 5 self.encoding=encoding 6 7 def __enter__(self): 8 # print('enter') 9 self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) 10 return self.f 11 12 def __exit__(self, exc_type, exc_val, exc_tb): 13 # print('exit') 14 self.f.close() 15 return True 16 def __getattr__(self, item): 17 return getattr(self.f,item) 18 19 with Open('a.txt','w') as f: 20 print(f) 21 f.write('aaaaaa') 22 f.asdfsdf #抛出异常,交给__exit__处理
5.元类(metaclass)

浙公网安备 33010602011771号