Python基础(十一)-面向对象四
一、上下文管理协议
1.1、什么叫上下文管理协议?
with open('a.txt') as f: '代码块'
即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
with语句小结:
with obj as f: '代码块' 1)with obj ==>触发obj.__enter__(),拿到返回值 2)as f ==> f=返回值 3)with obj as f ==> f=obj.__enter__() 4) 执行代码块 一:没有异常的情况下,整个代码块运行完毕后去触发__exit__,它的三个参数都为None 二:有异常的情况下,从异常出现的位置直接触发__exit__ a:如果__exit__的返回值为True,代表吞掉了异常 b:如果__exit__的返回值不为True,代表吐出了异常 c:__exit__的的运行完毕就代表了整个with语句的执行完毕
1.2、方法使用
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') return self def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') with Open('a.txt') as f: #触发__enter__方法 print('=====>执行代码块') print(f,f.name) #<__main__.Open object at 0x0000022813E55C88> a.txt
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type) print(exc_val) print(exc_tb) with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('异常产生') print('0'*100) #------------------------------->不会执行
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type) #<class 'AttributeError'> print(exc_val) #产生异常 print(exc_tb) #<traceback object at 0x000001E54419AF08> return True with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('产生异常') print('0'*100) #------------------------------->会执行
模拟open:
class Open: def __init__(self,filepath,mode='r',encoding='utf-8'): self.filepath=filepath self.mode=mode self.encoding=encoding def __enter__(self): print('enter') self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) #拿到文件句柄 return self.f def __exit__(self, exc_type, exc_val, exc_tb): # print('exit') self.f.close() return True def __getattr__(self, item): return getattr(self.f,item) with Open('a.txt','w') as f: #f==open(self.filepath,mode=self.mode,encoding=self.encoding) print(f) #<_io.TextIOWrapper name='a.txt' mode='w' encoding='utf-8'> f.write('aaaaaa') f.wasdf #抛出异常,交给__exit__处理
1.3、上下文管理用途
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
二、描述符
2.1、什么是描述符?
描述符本质就是一个新式类,在这个新式类中,至少实现__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
2.2、描述符是干什么的?
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
#描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') #描述符Int class Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People: name=Str() age=Int() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age p1=People('alex',18) #触发str类的__set__,触发int类的__set__ print("========") #描述符Str的使用 print(p1.name) #Str调用 None p1.name='egon' del p1.name #描述符Int的使用 p1.age #Int调用 p1.age=18 #Int设置... del p1.age #Int删除... print(p1.__dict__) #{} print(People.__dict__) #{'__init__': <function People.__init__ at 0x000001714E4CE620>, 'age': <__main__.Int object at 0x000001714E4D46D8>, '__module__': '__main__', 'name': <__main__.Str object at 0x000001714E4D46A0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'People' objects>} #补充 print(type(p1) == People) #True,type(obj)其实是查看obj是由哪个类实例化来的 print(type(p1).__dict__ == People.__dict__) #True
2.3、描述符的种类
1)数据描述符:至少实现了__get__()和__set__()
class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get')
2)非数据描述符:没有实现__set__()
class Foo: def __get__(self, instance, owner): print('get')
2.4、描述符注意事项
1)描述符本身应该定义成新式类,被代理的类也应该是新式类
2)必须把描述符定义成这个类的类属性,不能为定义到构造函数中
3)要严格遵循该优先级,优先级由高到底分别是
- 类属性
- 数据描述符
- 实例属性
- 非数据描述符
- 找不到的属性(触发__getattr__)
2.5、描述符优先级
1)类属性>数据描述符
#数据描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') class People: name=Str() def __init__(self,name,age): #name被Str类代理 self.name=name self.age=age #在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典 People.name #Str调用 ==>调用类属性name,本质就是在调用描述符Str,触发了__get__() People.name="AAA" #没有触发__set__ print(People.name) #AAA del People.name #没有触发__delete__ ''' 原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 People.name #调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() People.name='egon' #直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() del People.name #同上 '''
2)数据描述符>实例属性
#数据描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') class People: name=Str() def __init__(self,name,age): #name被Str类代理 self.name=name self.age=age p1=People('egon',18) #触发__set__ #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性 p1.name="AAA" #触发__set__ p1.name #Str调用 print(p1.__dict__) #{'age': 18} ==>并没有name属性 ==>name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了 print(People.__dict__) #'name': <__main__.Str object at 0x00000202C1DF5D30> del p1.name #Str删除...
3)实例属性>非数据描述符
#非数据描述符 class Str: def __get__(self, instance, owner): print('Str调用') class People: name=Str() def __init__(self,name,age): #name被Str类代理 self.name=name self.age=age p1=People('egon',18) print(p1.name) #egon #没有触发__get__方法 print(p1.__dict__) #{'age': 18, 'name': 'egon'}
4)非数据描述符>找不到
class Foo: def func(self): print('我胡汉三又回来了') def __getattr__(self, item): print('找不到了当然是来找我啦',item) f1=Foo() f1.xxxxxxxxxxx #找不到了当然是来找我啦 xxxxxxxxxxx
2.6、描述符的使用
2.6.1、参数类型限制
python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
class Str: def __init__(self,name): self.name=name def __get__(self, instance, owner): print('get--->',instance,owner) return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) class People: name=Str('name') def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People('egon',18,3231.3) #set---> <__main__.People object at 0x00000274AA9B4668> egon #调用 print(p1.__dict__) #{'salary': 3231.3, 'age': 18, 'name': 'egon'} p1.name #get---> <__main__.People object at 0x000001BCB50B4668> <class '__main__.People'> print(p1.name) #egon #赋值 print(p1.__dict__) #{'age': 18, 'name': 'egon', 'salary': 3231.3} p1.name='egonlin' #触发set print(p1.__dict__) #{'age': 18, 'name': 'egonlin', 'salary': 3231.3} #删除 print(p1.__dict__) #{'salary': 3231.3, 'name': 'egonlin', 'age': 18} del p1.name #触发delete print(p1.__dict__) #{'salary': 3231.3, 'age': 18}
实例二:
class Str: def __init__(self,name): self.name=name def __get__(self, instance, owner): print('get--->',instance,owner) return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) class People: name=Str('name') def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary # 疑问:如果我用类名去操作属性呢 ==>没有初始化实例 People.name #报错,错误的根源在于类去操作属性时,会把None传给instance #修订__get__方法 class Str: def __init__(self,name): self.name=name def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) class People: name=Str('name') def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary print(People.name) #输出 # get---> None <class '__main__.People'> # <__main__.Str object at 0x0000020ABCE05D68>
实例三:
class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) if not isinstance(value,self.expected_type): raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) class People: name=Typed('name',str) age=Typed('name',int) salary=Typed('name',float) def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary # p1=People(123,18,3333.3) #TypeError: Expected <class 'str'> # p1=People('egon','18',3333.3) #TypeError: Expected <class 'int'> p1=People('egon',18,3333) #TypeError: Expected <class 'float'>
2.7、类的装饰器
1)类的装饰器--无参
def decorate(cls): print("类的装饰器") return cls @decorate #People=decorate(People) class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People("AAA",18,333.33)
2)类的装饰器--有参
def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>',kwargs) return cls return decorate @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People('egon',18,3333.3) #类的装饰器开始运行啦------> {'salary': <class 'float'>, 'age': <class 'int'>, 'name': <class 'str'>}
3)与描述符结合
class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) if not isinstance(value,self.expected_type): raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>',kwargs) for name,expected_type in kwargs.items(): setattr(cls,name,Typed(name,expected_type)) return cls return decorate @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary print(People.__dict__) #{'age': <__main__.Typed object at 0x000001C81FC155F8>, '__doc__': None, 'name': <__main__.Typed object at 0x000001C81FC156A0>, '__weakref__': <attribute '__weakref__' of 'People' objects>, 'salary': <__main__.Typed object at 0x000001C81FC15668>, '__dict__': <attribute '__dict__' of 'People' objects>, '__init__': <function People.__init__ at 0x000001C81FC0E620>, '__module__': '__main__'} p1=People('egon',18,3333.3) # p1=People('egon','18',3333.3) #TypeError: Expected <class 'int'>
2.8、描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性
描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
2.9、自定制@property
利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
1)@property回顾
class Room: def __init__(self,name,width,length): self.name=name self.width=width self.length=length @property def area(self): return self.width * self.length r1=Room('AA',1,1) print(r1.area) #1
2)自定制property
class Lazyproperty: def __init__(self,func): self.func=func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is None: #如果是类调用,会返回none ==>Room.area return self return self.func(instance) class Room: def __init__(self,name,width,length): self.name=name self.width=width self.length=length @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 def area(self): return self.width * self.length print(Room.__dict__) #==>相当于在类中添加了一个属性 ==>'area': <__main__.Lazyproperty object at 0x000001ED1F016C50>} r1=Room('alex',1,1) print(r1.__dict__) #{'length': 1, 'width': 1, 'name': 'alex'} print(r1.area) #先在实例属性中查找,没找到到类中查找,属性被描述符代理,触发__get__方法 print(Room.area) #<__main__.Lazyproperty object at 0x000001D492CF5358>
3)实现延迟计算功能
class Lazyproperty: def __init__(self,func): self.func=func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is None: return self else: print('--->') # print(self) #<__main__.Lazyproperty object at 0x0000024F8E435550> # print(self.func) #<function Room.area at 0x000001E9D740E488> value=self.func(instance) #self.func ===>传进来的area(),需要 将实例传进去 ==>值为1 # print(self.func.__name__) #area setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中 return value class Room: def __init__(self,name,width,length): self.name=name self.width=width self.length=length @Lazyproperty #area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符' def area(self): return self.width * self.length r1=Room('alex',1,1) # print(r1.__dict__) #{'width': 1, 'name': 'alex', 'length': 1} print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后触发了area的__get__方法 # print(r1.__dict__) #{'name': 'alex', 'area': 1, 'width': 1, 'length': 1} # print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算,直接在实例属性字典中查找
4)小小改动
class Lazyproperty: def __init__(self,func): self.func=func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is None: return self else: value=self.func(instance) instance.__dict__[self.func.__name__]=value return value # return self.func(instance) def __set__(self, instance, value): #实现了set方法,此时是数据描述符,优先级高于实例属性 print('hahahahahah') class Room: def __init__(self,name,width,length): self.name=name self.width=width self.length=length @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 def area(self): return self.width * self.length print(Room.__dict__) #==>'area': <__main__.Lazyproperty object at 0x000001D868D35550>} r1=Room('alex',1,1) print(r1.area) print(r1.area) print(r1.area) print(r1.area) #缓存功能失效,每次都去找描述符了,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了 #输出 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1
2.10、自制@classmethod
class ClassMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, def feedback(*args,**kwargs): print('在这里可以加功能啊...') return self.func(owner,*args,**kwargs) return feedback class People: name='AAA' @ClassMethod # say_hi=ClassMethod(say_hi) def say_hi(cls,msg): print('%s %s' %(cls.name,msg)) print(People.__dict__) #==>'say_hi': <__main__.ClassMethod object at 0x000001EF2EA16B00> People.say_hi('hello') p1=People() p1.say_hi('hello')
2.11、自制@staticmethod
class StaticMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, def feedback(*args,**kwargs): print('在这里可以加功能啊...') return self.func(*args,**kwargs) return feedback class People: @StaticMethod #say_hi=StaticMethod(say_hi) def say_hi(x,y,z): print('------>',x,y,z) print(People.__dict__) #==>'say_hi': <__main__.StaticMethod object at 0x000001C3D63C5518> People.say_hi(1,2,3) p1=People() p1.say_hi(4,5,6)
三、property使用
3.1、property本质
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo: @property def AAA(self): print('get的时候运行我啊') @AAA.setter def AAA(self,value): print('set的时候运行我啊') @AAA.deleter def AAA(self): print('delete的时候运行我啊') #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter f1=Foo() f1.AAA #get的时候运行我啊 f1.AAA='aaa' #set的时候运行我啊 del f1.AAA #delete的时候运行我啊 #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #第二种写法 class Foo: def get_AAA(self): print('get的时候运行我啊') def set_AAA(self,value): print('set的时候运行我啊') def delete_AAA(self): print('delete的时候运行我啊') AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 f1=Foo() f1.AAA f1.AAA='aaa' del f1.AAA
3.2、property使用
使用示例一:
class Goods: def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): self.original_price = value @price.deleter def price(self): del self.original_price obj = Goods() obj.price # 获取商品价格 obj.price = 200 # 修改商品原价 print(obj.price) del obj.price # 删除商品原价
四、元类
4.1、元类的引出
参考文档:https://www.cnblogs.com/linhaifeng/articles/8029564.html
python中一切都是对象,类本身也是一个对象,当使用class时,解释器会在加载class时就会创建一个对象(这里的对象值的是类而不是实例)
class Foo: pass f1=Foo() #使用type()查看类型 print(type(f1)) #<class '__main__.Foo'> print(type(Foo)) #<class 'type'> print(type(object)) #<class 'type'>
4.2、什么是元类
元类是类的类,是类的模板
元类是控制如何创建类的,正如类是创建对象的模板一样
元类的实例是类,正如类的实例是对象(f1对象是Foo类的一个实例,Foo类是type类的一个 实例)
type是python中一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类的实例
一个类如果没有声明自己的元类,默认他的类就是type,除了使用元类,用户也可以通过继承type来自定义元类
4.3、类的创建方式
方式一:
class Foo: def func(self): print("func") print(Foo) #<class '__main__.Foo'> print(Foo.__dict__) #{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, 'func': <function Foo.func at 0x0000024EDB86E158>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
方式二:
def func(self): print("func") x=1 Foo=type("Foo",(object,),{"func":func,"x":1}) print(Foo) #<class '__main__.Foo'> print(Foo.__dict__) #{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, 'x': 1, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'func': <function func at 0x0000019343BA7F28>}
4.4、自定制元类
分析版:
class MyType(type): def __init__(self,a,b,c): print('元类的构造函数执行') # print(a) #Foo # print(b) #() # print(c) #{'__module__': '__main__', '__init__': <function Foo.__init__ at 0x000001BE2AB9E400>, '__qualname__': 'Foo'} def __call__(self, *args, **kwargs): print('=-======>') print(self) #<class '__main__.Foo'> print(args,kwargs) #('alex',) {} obj=object.__new__(self) #object.__new__(Foo)-->f1 #创建Foo实例化出来的实例f1 # print(obj) #<__main__.Foo object at 0x0000020492F45550> self.__init__(obj,*args,**kwargs) #Foo.__init__(f1,*arg,**kwargs),传入参数 return obj #返回实例化的对象给f1 class Foo(metaclass=MyType): #Foo=MyType(Foo,'Foo',(),{})---》__init__ def __init__(self,name): self.name=name #f1.name=name print(Foo) #<class '__main__.Foo'> f1=Foo('alex') #实例() ==>调用父类的__call__方法 print(f1) #<__main__.Foo object at 0x00000133525E5550> print(f1.__dict__) #{'name': 'alex'}
精简版:
#通过断点分析 class MyType(type): def __init__(self,a,b,c): print('元类的构造函数执行') def __call__(self, *args, **kwargs): obj=object.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Foo(metaclass=MyType): def __init__(self,name): self.name=name f1=Foo('alex')
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!