python属性访问顺序 --描述符、__getattr__()

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/windy135/article/details/83893203

在Python中,访问一个属性的优先级顺序按照如下顺序:
1、_getattribute_()
2.类属性
3.数据描述符
4.实例属性
5.非数据描述符
6.__getattr__()方法

需要注意的是:类属性和实例属性属于不同的属性集,因此这里类属性排序并不严谨。

首先来说下什么是描述符。

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

下面来看一个示例:

class NoDataDesc(object):
    def __get__(self, noinstance, owner):
        print("get:nodatadesc.attr")
        return noinstance.freq*2


class DataDesc(object):
    def __get__(self, instance, owner):
        print("get:datadesc.attr")
        return instance.freq*4

    def __set__(self, instance, value):
        print("set:datadesc.attr")
        print(instance, value)  # 输出<__main__.Test object at 0x104400438> -1 (将test实例传了进来,然后下面给test.__dict__添加属性)
        # 当调用到数据描述符时(self.data_result = data), 顺序:1、取instance.freq值(=3) 2、取value的值(=-1) 3、计算instance.doc_freq的值(-3)
        instance.doc_freq = instance.freq*value

    def __delete__(self, instance):
         print("del datadesc.attr")
         raise BaseException


class Test(object):
    data_result = DataDesc()
    nodata_result = NoDataDesc()

    def __init__(self, freq, data, nodata):
        self.freq = freq
        self.data_result = data
        self.test_data = data
        self.nodata_result = nodata


if __name__ == '__main__':
    test = Test(3, -1, -1)  # self.data_result = data 调用DataDesc.__set__方法,设置instance.doc_freq的值

    # 在DataDesc类中给instance(test实例)绑定了doc_freq
    print(test.doc_freq)  # 输出-3
    # 查看实例test
    print(test)  # 输出<__main__.Test object at 0x104400438>
    # 看下test的属性集
    print(test.__dict__)  # 输出{'test_data': -1, 'nodata_result': -1, 'doc_freq': -3, 'freq': 3}

    # 数据描述符, 优先与实例属性集
    print(test.data_result)  # 输出12 (调用DataDesc.__get__方法,打印instance.freq*4的值)

    # 非数据描述符, 落后于test属性集, 因此取出_dict__中的nodata_result的值
    print(test.nodata_result)  # 输出-1

如何检测一个对象是不是描述符?
如果把问题换成——一个对象要满足什么条件,它才是描述符呢——那是不是回答就非常简单了?
答:只要定义了(set,get,delete)方法中的任意一种或几种,它就是个描述符。
那么,继续问:怎么判断一个对象定义了这三种方法呢?
立马有人可能就会回答:你是不是傻啊?看一下不就看出来了。。。
问题是,你看不到的时候呢?python内置的staticmethod,classmethod怎么看?
正确的回答应该是:看这个对象的_dict_。
要看对象的_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))

测试一下:

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

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

描述符有什么用和好处:
1)一般情况下不会用到,建议:先定基本的,以后真有需要再扩展。别贪玩。
2)可以在设置属性时,做些检测等方面的处理
3)缓存?
4)设置属性不能被删除?那定义_delete_方法,并raise 异常。
5)还可以设置只读属性
6)把一个描述符作为某个对象的属性。这个属性要更改,比如增加判断,或变得更复杂的时候,所有的处理只要在描述符中操作就行了。

猜你喜欢

转载自blog.csdn.net/windy135/article/details/83893203