python属性查找(attribute lookup)

  在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

  下面是测试代码:

  

 1 #coding=utf-8
 2 class DataDescriptor(object):
 3     def __init__(self, init_value):
 4         self.value = init_value
 5
 6     def __get__(self, instance, typ):
 7         return ‘DataDescriptor __get__‘
 8
 9     def __set__(self, instance, value):
10         print (‘DataDescriptor __set__‘)
11         self.value = value
12
13 class NonDataDescriptor(object):
14     def __init__(self, init_value):
15         self.value = init_value
16
17     def __get__(self, instance, typ):
18         return(‘NonDataDescriptor __get__‘)
19
20 class Base(object):
21     dd_base = DataDescriptor(0)
22     ndd_base = NonDataDescriptor(0)
23
24
25 class Derive(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‘ % key
36
37     def change_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 def main():
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_key
57     except Exception, e:
58         assert isinstance(e, AttributeError)
59
60 if __name__ == ‘__main__‘:
61     main()

  注意第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 import functools, time
 2 class cached_property(object):
 3     """ A property that is only computed once per instance and then replaces
 4         itself with an ordinary attribute. Deleting the attribute resets the
 5         property. """
 6
 7     def __init__(self, func):
 8         functools.update_wrapper(self, func)
 9         self.func = func
10
11     def __get__(self, obj, cls):
12         if obj is None: return self
13         value = obj.__dict__[self.func.__name__] = self.func(obj)
14         return value
15
16 class TestClz(object):
17     @cached_property
18     def complex_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     print t.complex_calc
26     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:

(1)Descriptor HowTo Guide, https://docs.python.org/2/howto/descriptor.html#descriptor-protocol

(2)Object attribute lookup in Python,  http://www.betterprogramming.com/object-attribute-lookup-in-python.html

(3)python __set__ __get__ 等解释, http://blog.csdn.net/huithe/article/details/7484606

时间: 2024-10-15 16:13:31

python属性查找(attribute lookup)的相关文章

python属性查找

python中执行obj.attr时,将调用特殊方法obj.__getattribute__('attr'),该方法执行搜索来查找该属性,通常涉及检查特性.查找实例字典.查找类字典以及搜索基类.如果搜索过程失败,最终会尝试调用类的__getattr__()方法.如果这也失败,则抛出AttributeError异常. 具体步骤如下: 1.如果attr是个特殊属性(例如python提供的),直接返回. 2.在obj.__class__.__dict__即类字典中查找attr,如果attr是个data

python属性查找 深入理解(attribute lookup)

在Python中,属性查找(attribute lookup)是比较复杂的,特别是涉及到描述符descriptor的时候. 在上一文章末尾,给出了一段代码,就涉及到descriptor与attribute lookup的问题.而get系列函数(__get__, __getattr__, __getattribute__) 也很容易搞晕,本文就这些问题简单总结一下. 首先,我们知道: python中一切都是对象,"everything is object",包括类,类的实例,数字,模块

python:属性查找与绑定方法

属性查找 类有两种属性:数据属性和函数属性 1.类的数据属性是所有对象共享的 # 类的数据属性是所有对象共享的,id是一样的 print(id(OldboyStudent.school)) # 4830576 print(id(s1.school)) # 4830576 print(id(s2.school)) # 4830576 print(id(s3.school)) # 4830576 2. 类的函数属性是绑定给对象用的,称为绑定到对象的方法 # 类的函数属性是绑定给对象使用的,obj.m

python之属性描述符与属性查找规则

描述符 import numbers class IntgerField: def __get__(self, isinstance, owner): print('获取age') return self.num def __set__(self, instance, value): print('设置age值时') if not isinstance(value, numbers.Integral): raise ValueError('int need') self.num = value

属性查找与绑定方法

属性查找 类有两种属性:数据属性和函数属性 1.类的数据属性是所有对象共享的 class LuffyStudent: school='luffycity' def learn(self): print('is learning') def eat(self): print('is sleeping') #后产生对象 stu1=LuffyStudent() stu2=LuffyStudent() stu3=LuffyStudent() print(id(stu1.school)) print(id

属性查找;绑定到对象的方法的特殊之处;对象之间的交互

四 属性查找 类有两种属性:数据属性和函数属性 1. 类的数据属性是所有对象共享的 2. 类的函数属性是绑定给对象用的 #类的数据属性是所有对象共享的,id都一样 print(id(OldboyStudent.school)) print(id(s1.school)) print(id(s2.school)) print(id(s3.school)) ''' 4377347328 4377347328 4377347328 4377347328 ''' #类的函数属性是绑定给对象使用的,obj.

再看属性查找

再看属性查找 结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢??? 属性查找的原则:对象->类->父类 切记:父类 不是 元类 在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看), 我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象StanfordTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object class Mym

python制作查找单词翻译的脚本

本人由于英语渣,在linux底下经常看文档,但是有没有想有道词典这种软件,所以遇到不懂的单词只能手动复制粘贴在网上查找,这样就很不方便,学了python之后,就试着自己尝试下个在命令行下查找单词翻译的脚本. 在我眼里,实现上面这个要求的脚本语言分几块完成: 一是选好一个单词翻译的网站,优先选择GET方法的(代码写起来比较方便),如果是POST方法也不是没有办法,只是要自己新建一个POST请求,为此我就找到了一个用GET方法查找单词的神器网站——在线翻译_在线词典_金山词霸_爱词霸英语,用起来就很

IOC容器特性注入第五篇:查找(Attribute)特性注入

前面几篇文章分别介绍:程序集反射查找,特性,容器,但它们之间贯穿起来,形成查找Attribute注入IOC容器,就得需要下面这个类帮忙: 1.DependencyAttributeRegistrator(依赖特性注入类),有它才能在引擎初始化的时候 查找Attribute 进行注入 public class DependencyAttributeRegistrator { #region Fields private readonly ITypeFinder _finder; private r