python找出值的属性_python属性查找(attribute lookup)

本文详细探讨了Python中属性查找的机制,涉及descriptor、__getattribute__、__getattr__等概念。通过实例代码展示了data descriptor和non-data descriptor的查找顺序,并分析了类属性和实例属性的查找过程。同时,文章还介绍了cached_property这一非数据描述符的用法,说明其在缓存计算结果中的作用。
摘要由CSDN通过智能技术生成

在Python中,属性查找(attribute lookup)是比较复杂的,特别是涉及到描述符descriptor的时候。

在上一文章末尾,给出了一段代码,就涉及到descriptor与attribute lookup的问题。而get系列函数(__get__, __getattr__, __getattribute__) 也很容易搞晕,本文就这些问题简单总结一下。

首先,我们知道:

python中一切都是对象,“everything is object”,包括类,类的实例,数字,模块

任何object都是类(class or type)的实例(instance)

如果一个descriptor只实现了__get__方法,我们称之为non-data descriptor, 如果同时实现了__get__ __set__我们称之为data descriptor。

按照python doc,如果obj是某个类的实例,那么obj.name首先调用__getattribute__。如果类定义了__getattr__方法,那么在__getattribute__抛出 AttributeError 的时候就会调用到__getattr__,而对于描述符(__get__)的调用,则是发生在__getattribute__内部的。官网文档是这么描述的

The implementation works through a precedence chain that gives data descriptors priority over instance variables, instance variables priority over non-data descriptors, and assigns lowest priority to __getattr__() if provided.

obj = Clz(), 那么obj.attr 顺序如下:

(1)如果“attr”是出现在Clz或其基类的__dict__中, 且attr是data descriptor, 那么调用其__get__方法, 否则

(2)如果“attr”出现在obj的__dict__中, 那么直接返回 obj.__dict__[‘attr‘], 否则

(3)如果“attr”出现在Clz或其基类的__dict__中

(3.1)如果attr是non-data descriptor,那么调用其__get__方法, 否则

(3.2)返回 __dict__[‘attr‘]

(4)如果Clz有__getattr__方法,调用__getattr__方法,否则

(5)抛出AttributeError

下面是测试代码:

cdec0645add3fc3c328197dda5c76203.png

81178cc93a2a3bb5048d90d76e7ec935.png

1 #coding=utf-8

2 classDataDescriptor(object):3 def __init__(self, init_value):4 self.value =init_value5

6 def __get__(self, instance, typ):7 return ‘DataDescriptor __get__‘

8

9 def __set__(self, instance, value):10 print (‘DataDescriptor __set__‘)11 self.value =value12

13 classNonDataDescriptor(object):14 def __init__(self, init_value):15 self.value =init_value16

17 def __get__(self, instance, typ):18 return(‘NonDataDescriptor __get__‘)19

20 classBase(object):21 dd_base =DataDescriptor(0)22 ndd_base =NonDataDescriptor(0)23

24

25 classDerive(Base):26 dd_derive =DataDescriptor(0)27 ndd_derive =NonDataDescriptor(0)28 same_name_attr = ‘attr in class‘

29

30 def __init__(self):31 self.not_des_attr = ‘I am not descriptor attr‘

32 self.same_name_attr = ‘attr in object‘

33

34 def __getattr__(self, key):35 return ‘__getattr__ with key %s‘ %key36

37 defchange_attr(self):38 self.__dict__[‘dd_base‘] = ‘dd_base now in object dict‘

39 self.__dict__[‘ndd_derive‘] = ‘ndd_derive now in object dict‘

40

41 defmain():42 b =Base()43 d =Derive()44 print ‘Derive object dict‘, d.__dict__

45 assert d.dd_base == "DataDescriptor __get__"

46 assert d.ndd_derive == ‘NonDataDescriptor __get__‘

47 assert d.not_des_attr == ‘I am not descriptor attr‘

48 assert d.no_exists_key == ‘__getattr__ with key no_exists_key‘

49 assert d.same_name_attr == ‘attr in object‘

50 d.change_attr()51 print ‘Derive object dict‘, d.__dict__

52 assert d.dd_base != ‘dd_base now in object dict‘

53 assert d.ndd_derive == ‘ndd_derive now in object dict‘

54

55 try:56 b.no_exists_key57 exceptException, e:58 assertisinstance(e, AttributeError)59

60 if __name__ == ‘__main__‘:61 main()

View Code

注意第50行,change_attr给实例的__dict__里面增加了两个属性。通过上下两条print的输出如下:

Derive object dict {‘same_name_attr‘: ‘attr in object‘, ‘not_des_attr‘: ‘I am not descriptor attr‘}

Derive object dict {‘same_name_attr‘: ‘attr in object‘, ‘ndd_derive‘: ‘ndd_derive now in object dict ‘, ‘not_des_attr‘: ‘I am not descriptor attr‘, ‘dd_base‘: ‘dd_base now in object dict ‘}

调用change_attr方法之后,dd_base既出现在类的__dict__(作为data descriptor), 也出现在实例的__dict__, 因为attribute lookup的循序,所以优先返回的还是Clz.__dict__[‘dd_base‘]。而ndd_base虽然出现在类的__dict__, 但是因为是nondata descriptor,所以优先返回obj.__dict__[‘dd_base‘]。其他:line48,line56表明了__getattr__的作用。line49表明obj.__dict__优先于Clz.__dict__

前面提到过,类的也是对象,类是元类(metaclass)的实例,所以类属性的查找顺序基本同上,区别在于第二步,由于Clz可能有基类,所以是在Clz及其基类的__dict__查找“attr"

文末,我们再来看一下这段代码。

1 importfunctools, time2 classcached_property(object):3 """A property that is only computed once per instance and then replaces4 itself with an ordinary attribute. Deleting the attribute resets the5 property."""

6

7 def __init__(self, func):8 functools.update_wrapper(self, func)9 self.func =func10

11 def __get__(self, obj, cls):12 if obj is None: returnself13 value = obj.__dict__[self.func.__name__] =self.func(obj)14 returnvalue15

16 classTestClz(object):17 @cached_property18 defcomplex_calc(self):19 print ‘very complex_calc‘

20 return sum(range(100))21

22 if __name__==‘__main__‘:23 t =TestClz()24 print ‘>>> first call‘

25 printt.complex_calc26 print ‘>>> second call‘

27 print t.complex_calc

cached_property是一个non-data descriptor。在TestClz中,用cached_property装饰方法complex_calc,返回值是一个descriptor实例,所以在调用的时候没有使用小括号。

第一次调用t.complex_calc之前,obj(t)的__dict__中没有”complex_calc“, 根据查找顺序第三条,执行cached_property.__get__, 这个函数代用缓存的complex_calc函数计算出结果,并且把结果放入obj.__dict__。那么第二次访问t.complex_calc的时候,根据查找顺序,第二条有限于第三条,所以就直接返回obj.__dict__[‘complex_calc‘]。bottle的源码中还有两个descriptor,非常厉害!

references:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值