zoukankan      html  css  js  c++  java
  • python——描述符

    本文主要介绍描述符的定义,个人的一些理解;什么是数据描述符;什么是非数据描述符;描述符的检测等。希望看完这篇文章后,你对描述符有了更清晰的认识。知道怎么判断一个对象是不是描述符,知道如果定义一个描述符,知道什么是该用描述符。当然,最大的目的是,通过学习描述符,让你对python这门语言有更多深入的认识。

    什么是描述符

    官方的定义:描述符是一种具有“捆绑行为”的对象属性。访问(获取、设置和删除)它的属性时,实际是调用特殊的方法(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)

    数据描述符:定义了setget方法的对象
    非数据描述符:只定义了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()
        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 test.data_result12345678910111213141516171819202122232425
    

    注释print test.data_result的时候打印的是:

    set:datadesc.attr
    -1(这个值是实体的值,相当于打印test.dict[” nodata_result “])
    “set:datadesc.attr”是
    self.data_result = data
    时打印的。

    注释print test.nodata_result的输出:

    set:datadesc.attr
    datadesc.attr
    12(这个是描述符set中设置的值。相当于data_result.get(test,3),返回的是12)

    谈到这个搜索链的顺序,稍微再引申一下。问一个问题:

    如果Test这个类还有getattr,getattribute 呢?

    这里就不多介绍getattr,getattribute (每一个都可以另外再起一篇文章了)。熟悉的记住一点就行:

    getattribute优先级是最高的,而getattr是最低的。

    如何检测一个对象是不是描述符

    如果把问题换成——一个对象要满足什么条件,它才是描述符呢——那是不是回答就非常简单了?
    答:只要定义了(set,get,delete)方法中的任意一种或几种,它就是个描述符。
    那么,继续问:怎么判断一个对象定义了这三种方法呢?
    立马有人可能就会回答:你是不是傻啊?看一下不就看出来了。。。
    问题是,你看不到的时候呢?python内置的staticmethod,classmethod怎么看?
    正确的回答应该是:看这个对象的dict
    写到这里,我得先停一下,来解释一个问题。不知道读者有没有发现,上面我一直再说“对象”,“对象”,而实际上指的明明是一个类啊?博主是不是搞错了呢?嗯,作为一名入门语言是java(抛开只玩过,没写过软件的C)的程序猿,把类称之为对象,的确让我本人也有点别扭。但是,在python中,这样称呼又是妥当的。因为,“一切皆对象”,类,不过也是元类的一种对象而已。
    跑了下题,接着说。要看对象的dict好办,直接dir(对象)就行了。现在可以写出检测对象是不是描述符的方法了:

    def has_descriptor_attrs(obj):
        return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
    
    def is_descriptor(obj):
        """obj can be instance of descriptor or the descriptor class"""
        return bool(has_descriptor_attrs(obj))
    
    def has_data_descriptor_attrs(obj):
        return set(['__set__', '__delete__']) & set(dir(obj))
    
    def is_data_descriptor(obj):
        return bool(has_data_descriptor_attrs(obj))
    12345678910111213
    

    测试一下:

    print is_descriptor(classmethod), is_data_descriptor(classmethod)
    print is_descriptor(staticmethod), is_data_descriptor(staticmethod)
    print  is_data_descriptor(property)123
    

    输出:
    (True, False)
    (True, False)
    True
    看来,特性(property)是数据描述符

    描述符有什么用和好处

    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()12345678910111213141516171819202122232425262728293031323334353637383940
    

    打印结果:

    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 BaseException12345678910111213
    

    这样呢:

        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 = ""12345678910
    

    也正确,但student变成类成员,不会是实体成员,打印的结果会全部一样(都是:{u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}

    总结

    你可能还从没有在实际工作中写过一个描述符,甚至觉得今后也极少有机会会用的。但是,不可避免地,你每天(只要是在写python代码)都或多或少的要和描述符打交道。要非常好的理解描述符并不是一件容易的事,希望这篇这篇文章能够在一定程度上帮到你。相信我,好好理解下描述符会让你对python的认识提高不少。

  • 相关阅读:
    补充 函数详解
    Python web前端 11 form 和 ajax
    进程线程之间的通信
    面向对象epoll并发
    socket发送静态页面
    进程与线程的表示,属性,守护模式
    并发
    django, tornado
    并行
    非阻塞套接字编程, IO多路复用(epoll)
  • 原文地址:https://www.cnblogs.com/huang-yc/p/9742369.html
Copyright © 2011-2022 走看看