描述符

描述符(__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)

 

 

 

 

posted @ 2018-06-26 22:12  阿谋  阅读(305)  评论(0编辑  收藏  举报