python

python描述符

文章暂存

systemime
2020-08-09
7 min

摘要.

一句话解释: 描述符是一个用于维护单一属性的类
描述符2个部分: 
1.描述符介绍
2.描述符实际应用
描述符是一个独立的类.
特性(property,在另一篇文章中已经介绍并讲解2种方式)是描述符的一种.
描述符用于对属性的拦截 (获取/修改(设置)/ 删除)
具体来说,描述符的作用在于, 对某个特定的属性在获取/删除/设置 方面拦截了一下,
给与用户自己来定义的一个过程
切记切记,描述符实例是用于赋给一个类属性的,在下面讲解
_

一个简单的描述符:

1. class Descriptor:
2. def __get__(self, instance, owner):pass
3. def __set__(self, instance, value):pass
4. def __delete__(self, instance):pass

含有任何3个方法其中的一种就可以看作是一个描述符.
get : 获取, set :设置/修改 , __delete:删除

1. #描述符
2. #用于单一属性的获取,设置/修改 , 删除
3. #是一个类
4. #类中含有__get__, __set__ , __delete__ 方法的可以看作一个描述符
5. class D:      #创建一个类, 添加了一个__get__ 方法
6. def __get__(self, instance, owner):     #参数: 自身实例, 所附加的客户类实例,附加到的类
7. #3个参数无需手动传递
8.         print(self,instance,owner,sep='\t')
9. 
10. class Sub:
11.     attr = D()      #添加了一个属性, Sub.attr , 赋值为一个 描述符
12. s = Sub()
13. s.attr              #当获取attr时. 由于attr是一个描述符 => D.__get__(Sub.attr, s, Sub)
14. Sub.attr
15. #echo:
16. #<__main__.D object at 0x00608DF0>	<__main__.Sub object at 0x00608ED0>	<class '__main__.Sub'>
17. #<__main__.D object at 0x00608DF0>	None	<class '__main__.Sub'>
1. #描述符的坑 , 与python的赋值相关
2. s.attr = '123'         #你想为原attr 赋值吗? 实际是s 实例新增了一个 attr 属性
3. print(s.__dict__)

当描述符含有__set__ 方法时, 实例将不再使用常规的层级赋值行为

1. #给描述符赋值 ,绕过常规的爬属性: 在描述符类中添加一个__set__
2. class D:
3. def __get__(self, instance, owner):
4.         print(D.__get__.__name__ , ' invoked')
5. def __set__(self, instance, value):
6.         print(D.__set__.__name__, ' invoked' , ',value:' ,value)
7. #raise AttributeError("不能设置")   如果想attr 属性只读 , 可以在set中抛出异常
8. class C:
9.     attr = D()
10. 
11. c = C()
12. c.attr = 1      #当描述符中含有__set__ 可以赋值 ,展开表达式: D.__set__(C.attr,c,C)

接下来一个完整的实际的例子

1. #描述符对象
2. #一个描述符对象维护一个属性的获取,设置/修改,删除
3. #切记切记,描述符实例是用于赋给一个类属性的. 如果赋给实例属性,就无法工作了
4. class Name:
5. "描述符文档"
6. def __get__(self, instance, owner):             #取值
7.         print(Name.__get__.__name__, ' invoked!')
8. return instance._name
9. def __set__(self, instance, value):             #赋值
10.         print(Name.__set__.__name__, ' invoked!')
11.         instance._name = value
12. def __delete__(self, instance):
13.         print(Name.__delete__.__name__ , " invoked!") #删除属性
14. del instance._name
15. 
16. class Person:
17. def __init__(self , name = "Person"):
18.         self._name = name
19.     name = Name()           #当实例使用 name 属性时,将触发描述符对象的使用
20. 
21. p = Person()
22. print(p.name)               #触发 Name.__get__(Person.name,p , Person)
23. p.name = 'hi'              #触发 Name.__set__(Person.name,p,'hi')
24. del p.name                 #触发 Name.__delete__(Person.name,p)
25. #echo
26. #__get__  invoked!
27. #Person
28. #__set__  invoked!
29. #__delete__  invoked!
1. #同样的如果一个描述类只作用于 某个特殊的类 , 可以使用嵌套类 来实现
2. class Person:
3. def __init__(self,value = 'Person'):
4.         self._name = value
5. #把描述符类写在内部 , 其他没变
6. class DescriptorName:
7. def __get__(self, instance, owner):
8.             print(self.__class__.__name__,' __get__')
9. return instance._name
10. def __set__(self, instance, value):
11.             print(self.__class__.__name__,'__set__')
12.             instance._name = value
13. def __delete__(self, instance):
14.             print(self.__class__.__name__,'__delete__')
15. del instance._name
16.     name = DescriptorName()         #产生一个描述符对象
17. p1 = Person()
18. p1.name

当要把一个属性特殊化时,可以考虑使用描述符,这样当使用这个属性时,实际是使用了描述符对象来进行处理;
下面一些具体的例子,  给描述符类添加__init__ , 描述符也是一个普通的类,也可以添加属性

1. #描述符对象
2. class DescState:
3. #添加一个init , 来初始化描述符自身的属性value
4. def __init__(self,value):
5.         self.value = value
6. def __get__(self, instance, owner):
7.         print(DescState.__name__,DescState.__get__.__name__,'invoked!',sep='\t')
8. return self.value
9. def __set__(self, instance, value):
10.         print(DescState.__name__,DescState.__get__.__name__, ' invoked!',sep='\t')
11.         self.value = value
12. 
13. #描述符对象
14. #假设被附加到的类实例有个属性_name
15. class InstState:
16. def __get__(self, instance, owner):
17.         print(InstState.__name__,'__get__ invoked!')
18. return instance._name               #假设实例有个属性: _name
19. def __set__(self, instance, value):
20.         print(InstState.__name__,'__set__ invoked!')
21.         instance._name = value
1. #一个客户类
2. #attr_desc 自身拥有一个属性
3. #attr_inst 将给实例本身的属性:_name 赋值,取值
4. #实例本身拥有2个属性, _name, _instAttr
5. class ClientClass:
6.     attr_desc = DescState(10)       #创建一个描述符对象,并赋予初始值,这个描述符对象有自己的属性
7.     attr_inst = InstState()         #创建一个描述符对象,这个描述符对象将修改与返回实例的属性:_name
8. def __init__(self,name = 'ClientClass'):
9.         self._name = name           #2个实例属性
10.         self._instAttr = 1
11. 
12. obj = ClientClass()
13. #使用3个不同的属性,分别输出
14. #表达式展开: 1.DescState.__get__() 2.InstState.__get__()
15. print(obj.attr_desc ,obj.attr_inst,obj._instAttr)
16. #echo:
17. #DescState	__get__	invoked!
18. #InstState __get__ invoked!
19. #10 ClientClass 1
20. 
21. obj.attr_desc = 5           #DescState.__set__()
22. obj.attr_inst = 6           #InstState.__set__()
23. obj._instAttr = 7
24. print(obj.attr_desc ,obj.attr_inst,obj._instAttr)
25. #echo:
26. #DescState	__set__	 invoked!
27. #InstState __set__ invoked!
28. #DescState	__get__	invoked!
29. #InstState __get__ invoked!
30. #5 6 7
上次编辑于: 5/20/2021, 7:26:49 AM