描述符是什么
- 官方的定义:
描述符是一种具有“捆绑行为”的对象属性。
访问(获取、设置和删除)它的属性时,实际是调用特殊的方法(_get_(),_set_(),_delete_()
)。
也就是说,如果一个对象定义了这三种方法的任何一种,它就是一个描述符。
- 更多的理解:
通常情况下,访问一个对象的搜索链是怎样的?比如a.x,首先,应该是查询a._dict_[‘x’]
,然后是type(a)._dict_[‘x’]
,一直向上知道元类那层止(不包括元类)。如果这个属性是一个描述符呢?那python就会“拦截”这个搜索链,取而代之调用描述符方法(_get_
)。_get_(self, instance, owner)
— 获取属性时调用,返回设置的属性值,通常是_set_
中的value,或者附加的其他组合值。_set_(self, instance, value)
— 设置属性时调用,返回None._delete_(self, instance)
— 删除属性时调用,返回None
其中,instance是这个描述符属性所在的类的实体,而owner是描述符所在的类。
数据描述符(data descriptor)和非数据描述符(non-data descriptors)
- 数据描述符:定义了
_set_
和_get_
方法的对象- 非数据描述符:只定义了
_get_
方法的对象。通常方法都是非数据描述符。比如后面会谈到的staticmethod
,classmethod
等。
区别:
1、位于搜索链上的顺序。
搜索链(或者优先链)的顺序大概是这样的:
数据描述符>实体属性(存储在实例化对象的_dict_中)>非数据描述符。
这个顺序初看起来挺晦涩。解释如下:
获取一个属性的时候:
- 首先,看这个属性是不是一个数据描述符,如果是,就直接执行描述符的
_get_
方法,并返回值。- 其次,如果这个属性不是数据描述符,那就按常规去从实例化对象的
_dict_
里面取。- 最后,如果
_dict_
里面还没有,但这是一个非数据描述符
,则执行非数据描述符的_get_
方法,并返回。
举例:
class NoDataDesc(object):
def __get__(self, instance, owner):
print ("nodatadesc.attr")
return instance.freq*2
class DataDesc(object):
def __get__(self, instance, owner):
print ("datadesc.attr")
return instance.freq*4
def __set__(self, instance, value):
print ("set:datadesc.attr")
instance.doc_freq = instance.freq*value
def __delete__(self, instance):
print ("del datadesc.attr")
raise BaseException
class Test(object):
data_result = DataDesc()
nodata_result = DataDesc()
freq=NoDataDesc()
def __init__(self,freq,data,nodata):
self.freq = freq
self.data_result = data
self.nodata_result = nodata
test = Test(3,-1,-1)
print (test.nodata_result)
print 输出结果:
set:datadesc.attr
set:datadesc.attr
datadesc.attr
12(这个是描述符_set_中设置的值。相当于data_result._get_(test,3),返回的是12)
如何检测一个对象是不是描述符
如果把问题换成——一个对象要满足什么条件,它才是描述符呢——那是不是回答就非常简单了?
答:只要定义了(_set_,_get_,_delete_
)方法中的任意一种或几种,它就是个描述符。
那么,继续问:怎么判断一个对象定义了这三种方法呢?
立马有人可能就会回答:你是不是傻啊?看一下不就看出来了。。。
问题是,你看不到的时候呢?python内置的staticmethod,classmethod怎么看?
正确的回答应该是:看这个对象的_dict_
。
描述符有什么用和好处
- 1)一般情况下不会用到,建议:先定基本的,以后真有需要再扩展。别贪玩。
- 2)可以在设置属性时,做些检测等方面的处理
- 3)缓存?
- 4)设置属性不能被删除?那定义delete方法,并raise 异常。
- 5)还可以设置只读属性
- 6)把一个描述符作为某个对象的属性。这个属性要更改,比如增加判断,或变得更复杂的时候,所有的处理只要在描述符中操作就行了。
例子
1、一个学校类,学校类又一个学生属性,赋给这个属性的值是一个json格式的字符串。需要json解析。现在是要处理学校很多学生的数据。要求:不能因为某个学生属性有问题而导致其他学生的数据有问题。并且这个学生属性所在这条记录要保存,对于它的属性,置空。
代码如下:
class Student(object):
def __init__(self):
self.inst = {}
def __get__(self, instance, owner):
return self.inst[instance]
def __set__(self, instance, value):
try:
self.inst[instance] = json.loads(value)
except ValueError,e:
print "error"
self.inst[instance] = ""
def __delete__(self, instance):
raise BaseException
class School(object):
student = Student()
def __init__(self,tid,title,student):
self.tid = tid
self.name = title
self.student = student
def main():
lst = []
for i in xrange(10):
student = '{"aturbo":{"english":"100","math":"100"}}'
if i ==1:
student = '{"sansan":{"english":"40","scid":"20"}}'
if i == 3:
#print i
student ='{"sansan"}'
scl = School(str(i),"fege",student)
lst.append(scl)
for info in lst:
print info.tid,':',info.student
if __name__ =="__main__":
main()
打印结果:
0 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
1 : {u’sansan’: {u’scid’: u’20’, u’english’: u’40’}}
2 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
3 :
4 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
5 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
6 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
7 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
8 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
9 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
注意两点:
1)tid为1这条的取值,tid=3这条的取值
2)这样定义Student这个描述符也可以:
class Student(object):
def __get__(self, instance, owner):
return instance.value
def __set__(self, instance, value):
try:
instance.value = json.loads(value)
except ValueError,e:
print "error"
instance.value = ""
def __delete__(self, instance):
raise BaseException
这样呢:
class Student(object):
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
try:
self.value = json.loads(value)
except ValueError, e:
print "error"
self.value = ""
也正确,但student变成类成员,不会是实体成员,打印的结果会全部一样(都是:{u’aturbo’: {u’math’: u’100’, u’english’: u’100’}})
总结
你可能还从没有在实际工作中写过一个描述符,甚至觉得今后也极少有机会会用的。但是,不可避免地,你每天(只要是在写python代码)都或多或少的要和描述符打交道。要非常好的理解描述符并不是一件容易的事,希望这篇这篇文章能够在一定程度上帮到你。相信我,好好理解下描述符会让你对python的认识提高不少。