面向对象学习目录
六、描述符(__get__,__set__,__delete__)
1 描述符是什么:
描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
定义一个描述符
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='egon' 12 f1.name 13 del f1.name 14 #疑问:何时,何地,会触发这三个方法的执行
描述符应用之何时?何地?
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() 21 age=Int() 22 def __init__(self,name,age): #name被Str类代理,age被Int类代理, 23 self.name=name 24 self.age=age 25 26 # 何地?:定义成另外一个类的类属性 27 28 # 何时?:且看下列演示 29 30 p1=People('alex',18) 31 32 # 描述符Str的使用 33 p1.name 34 p1.name='egon' 35 del p1.name 36 37 # 描述符Int的使用 38 p1.age 39 p1.age=18 40 del p1.age 41 42 # 我们来瞅瞅到底发生了什么 43 print(p1.__dict__) 44 print(People.__dict__) 45 46 # 补充 47 print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的 48 print(type(p1).__dict__ == People.__dict__)
3 描述符分两种
一 数据描述符:
至少实现了__get__()和__set__()
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get')
二 非数据描述符:
没有实现__set__()
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get')
4 注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
类属性>数据描述符
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 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 19 #那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,没错 20 People.name #恩,调用类属性name,本质就是在调用描述符Str,触发了__get__() 21 22 People.name='egon' #那赋值呢,我去,并没有触发__set__() 23 del People.name #赶紧试试del,我去,也没有触发__delete__() 24 #结论:描述符对类没有作用-------->傻逼到家的结论 25 26 ''' 27 原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 28 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() 29 30 People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() 31 del People.name #同上 32 '''
数据描述符>实例属性
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 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 p1=People('egon',18) 18 19 #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性 20 p1.name='egonnnnnn' 21 p1.name 22 print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了 23 del p1.name
实例属性>非数据描述符
1 class Foo: 2 def func(self): 3 print('我胡汉三又回来了') 4 5 f1=Foo() 6 f1.func() 7 #调用类的方法,也可以说是调用非数据描述符 8 #函数是一个非数据描述符对象(一切皆对象么) 9 print(dir(Foo.func)) 10 print(hasattr(Foo.func,'__set__')) 11 print(hasattr(Foo.func,'__get__')) 12 print(hasattr(Foo.func,'__delete__')) 13 #有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了 14 #笨蛋哥,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么 15 #函数就是一个由非描述符类实例化得到的对象 16 #没错,字符串也一样 17 18 f1.func='这是实例属性啊' 19 print(f1.func) 20 21 del f1.func #删掉了非数据 22 f1.func()
再次验证:实例属性>非数据描述符
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get') 6 7 class Room: 8 name=Foo() 9 def __init__(self,name,width,length): 10 self.name=name 11 self.width=width 12 self.length=length 13 14 15 #name是一个数据描述符,因为name=Foo()而Foo实现了get和set方法,因而比实例属性有更高的优先级 16 #对实例的属性操作,触发的都是描述符的 17 r1=Room('厕所',1,1) 18 r1.name 19 r1.name='厨房'
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get') 4 5 class Room: 6 name=Foo() 7 def __init__(self,name,width,length): 8 self.name=name 9 self.width=width 10 self.length=length 11 12 13 #name是一个非数据描述符,因为name=Foo()而Foo没有实现set方法,因而比实例属性有更低的优先级 14 #对实例的属性操作,触发的都是实例自己的 15 r1=Room('厕所',1,1) 16 r1.name 17 r1.name='厨房'
非数据描述符>找不到
1 class Foo: 2 def func(self): 3 print('我胡汉三又回来了') 4 5 def __getattr__(self, item): 6 print('找不到了当然是来找我啦',item) 7 8 f1=Foo() 9 f1.xxxxxxxxxxx
5 描述符使用
众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
牛刀小试
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 return instance.__dict__[self.name] 7 def __set__(self, instance, value): 8 print('set--->',instance,value) 9 instance.__dict__[self.name]=value 10 def __delete__(self, instance): 11 print('delete--->',instance) 12 instance.__dict__.pop(self.name) 13 14 15 class People: 16 name=Str('name') 17 def __init__(self,name,age,salary): 18 self.name=name 19 self.age=age 20 self.salary=salary 21 22 p1=People('egon',18,3231.3) 23 24 #调用 25 print(p1.__dict__) 26 p1.name 27 28 #赋值 29 print(p1.__dict__) 30 p1.name='egonlin' 31 print(p1.__dict__) 32 33 #删除 34 print(p1.__dict__) 35 del p1.name 36 print(p1.__dict__)
拔刀相助
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 return instance.__dict__[self.name] 7 8 def __set__(self, instance, value): 9 print('set--->',instance,value) 10 instance.__dict__[self.name]=value 11 def __delete__(self, instance): 12 print('delete--->',instance) 13 instance.__dict__.pop(self.name) 14 15 16 class People: 17 name=Str('name') 18 def __init__(self,name,age,salary): 19 self.name=name 20 self.age=age 21 self.salary=salary 22 23 #疑问:如果我用类名去操作属性呢 24 People.name #报错,错误的根源在于类去操作属性时,会把None传给instance
修订__get__方法
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 if instance is None: # 方便直接用类去调用属性时,不会报错,会返回一个Str对象 7 return self 8 return instance.__dict__[self.name] 9 10 def __set__(self, instance, value): 11 print('set--->',instance,value) 12 instance.__dict__[self.name]=value 13 def __delete__(self, instance): 14 print('delete--->',instance) 15 instance.__dict__.pop(self.name) 16 17 18 class People: 19 name=Str('name') 20 def __init__(self,name,age,salary): 21 self.name=name 22 self.age=age 23 self.salary=salary
24 25 print(People.name) #完美,解决
磨刀霍霍
1 class Str: 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 21 class People: 22 name=Str('name',str) #新增类型限制str 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) #传入的name因不是字符串类型而抛出异常
大刀阔斧
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 21 class People: 22 name=Typed('name',str) 23 age=Typed('name',int) 24 salary=Typed('name',float) 25 def __init__(self,name,age,salary): 26 self.name=name 27 self.age=age 28 self.salary=salary 29 30 p1=People(123,18,3333.3) 31 p1=People('egon','18',3333.3) 32 p1=People('egon',18,3333)
大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,low,这时候我需要教你一招:独孤九剑
类的装饰器:无参
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('egon',18,3333.3)
类的装饰器:有参
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)
终极大招
刀光剑影
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 28 @typeassert(name=str,age=int,salary=float) 29 # 有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 30 # 2.People=decorate(People) 31 class People: 32 def __init__(self,name,age,salary): 33 self.name=name 34 self.age=age 35 self.salary=salary 36 37 print(People.__dict__) 38 p1=People('egon',18,3333.3)
6 描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
7 利用描述符原理完成一个自定制@property,实现延迟计算
(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
@property回顾
1 class Room: 2 def __init__(self,name,width,length): 3 self.name=name 4 self.width=width 5 self.length=length 6 7 @property 8 def area(self): 9 return self.width * self.length 10 11 r1=Room('alex',1,1) 12 print(r1.area)
自己做一个@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) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
一个小的改动,延迟计算的美梦就破碎了
1 #缓存不起来了 2 class Lazyproperty: 3 def __init__(self,func): 4 self.func=func 5 def __get__(self, instance, owner): 6 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 7 if instance is None: 8 return self 9 else: 10 value=self.func(instance) 11 instance.__dict__[self.func.__name__]=value 12 return value 13 # return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情 14 def __set__(self, instance, value): 15 print('hahahahahah') 16 17 class Room: 18 def __init__(self,name,width,length): 19 self.name=name 20 self.width=width 21 self.length=length 22 23 @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 24 def area(self): 25 return self.width * self.length 26 27 print(Room.__dict__) 28 r1=Room('alex',1,1) 29 print(r1.area) 30 print(r1.area) 31 print(r1.area) 32 print(r1.area) #缓存功能失效,每次都去找描述符了,为何,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
8 利用描述符原理完成一个自定制@classmethod
自己做一个@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='linhaifeng' 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 class ClassMethod: 23 def __init__(self,func): 24 self.func=func 25 26 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 27 def feedback(*args,**kwargs): 28 print('在这里可以加功能啊...') 29 return self.func(owner,*args,**kwargs) 30 return feedback 31 32 class People: 33 name='linhaifeng' 34 @ClassMethod # say_hi=ClassMethod(say_hi) 35 def say_hi(cls,msg): 36 print('你好啊,帅哥 %s %s' %(cls.name,msg)) 37 38 People.say_hi('你是那偷心的贼') 39 40 p1=People() 41 p1.say_hi('你是那偷心的贼')
9 利用描述符原理完成一个自定制的@staticmethod
自己做一个@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)