python属性查找 深入理解(attribute lookup)
在上一文章末尾,给出了一段代码,就涉及到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。
实例属性查找
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.
(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
#coding=utf-8
class DataDescriptor(object):
def __init__(self, init_value):
self.value = init_value def __get__(self, instance, typ):
return 'DataDescriptor __get__' def __set__(self, instance, value):
print ('DataDescriptor __set__')
self.value = value class NonDataDescriptor(object):
def __init__(self, init_value):
self.value = init_value def __get__(self, instance, typ):
return('NonDataDescriptor __get__') class Base(object):
dd_base = DataDescriptor(0)
ndd_base = NonDataDescriptor(0) class Derive(Base):
dd_derive = DataDescriptor(0)
ndd_derive = NonDataDescriptor(0)
same_name_attr = 'attr in class' def __init__(self):
self.not_des_attr = 'I am not descriptor attr'
self.same_name_attr = 'attr in object' def __getattr__(self, key):
return '__getattr__ with key %s' % key def change_attr(self):
self.__dict__['dd_base'] = 'dd_base now in object dict '
self.__dict__['ndd_derive'] = 'ndd_derive now in object dict ' def main():
b = Base()
d = Derive()
print 'Derive object dict', d.__dict__
assert d.dd_base == "DataDescriptor __get__"
assert d.ndd_derive == 'NonDataDescriptor __get__'
assert d.not_des_attr == 'I am not descriptor attr'
assert d.no_exists_key == '__getattr__ with key no_exists_key'
assert d.same_name_attr == 'attr in object'
d.change_attr()
print 'Derive object dict', d.__dict__
assert d.dd_base != 'dd_base now in object dict '
assert d.ndd_derive == 'ndd_derive now in object dict ' try:
b.no_exists_key
except Exception, e:
assert isinstance(e, AttributeError) if __name__ == '__main__':
main()
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__
cached_property例子
我们再来看看上一文章的这段代码。
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实例,所以在调用的时候没有使用小括号。
类属性查找
前面提到过,类的也是对象,类是元类(metaclass)的实例,所以类属性的查找顺序基本同上。区别在于第二步,由于Clz可能有基类,所以是在Clz及其基类的__dict__”查找“attr,注意这里的查找并不是直接返回clz.__dict__['attr']。具体来说,这第二步分为以下两种情况:
(2.1)如果clz.__dict__['attr']是一个descriptor(不管是data descriptor还是non-data descriptor),都调用其__get__方法
(2.2)否则返回clz.__dict__['attr']
这就解释了一个很有意思的问题:method与function的问题
>>> class Widget(object):
... def func(self):
... pass
...
>>> w = Widget()
>>> Widget.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Widget' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Widget' objects>, '__doc__': None, 'func': <function func at 0x7fdc7d0d1668>})
>>> w.__dict__
{}>>> Widget.__dict__['func']
<function func at 0x7fdc7d0d1668>
>>> Widget.func
<unbound method Widget.func>
>>>
Widget是一个之定义了一个func函数的类,func是类的属性,这个也可以通过Widget.__dict__、w.__dict__看到。Widget.__dict__['func']返回的是一个function,但Widget.func是一个unbound method,即Widget.func并不等同于Widget.__dict__['func'],按照前面的类属性的访问顺序,我们可以怀疑,func是一个descriptor,这样才不会走到第2.2这种情况。验证如下:
>>> dir(Widget.__dict__['func'])
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
属性赋值
Python的属性赋值(attribute assignment)也会受到descriptor(data descriptor)的影响,同时也会受到__setattr__函数的影响。当然Python中还有一个setattr,setattr(x, 'foobar', 123)等价于x.foobar = 123,二者都叫attribute assignment。
首先看看__setattr__:
object.__setattr__(self, name, value)
Called when an attribute assignment is attempted. This is called instead of the normal mechanism
那什么是normal mechanism,简单来说就是x.__dict__['foobar'] = 123,不管'foobar'之前是否是x的属性(当然赋值之后就一定是了)。但是如果‘’foobar‘’是类属性,且是data descriptor,那么回优先调用__set__。我们来看一个例子:
class MaxValDes(object):
def __init__(self, attr, max_val):
self.attr = attr
self.max_val = max_val def __get__(self, instance, typ):
return instance.__dict__[self.attr] def __set__(self, instance, value):
instance.__dict__[self.attr] = min(self.max_val, value)
print 'MaxValDes __set__', self.attr, instance.__dict__[self.attr] class Widget(object):
a = MaxValDes('a', 10)
def __init__(self):
self.a = 0 # def __setattr__(self, name, value):
# self.__dict__[name] = value
# print 'Widget __setattr__', name, self.__dict__[name] if __name__ == '__main__':
w0 = Widget()
w0.a = 123
输出如下:
MaxValDes __set__ a 0
MaxValDes __set__ a 10
可以看到,即使Widget的实例也有一个‘a’属性,但是调用w.a的时候会调用类属性‘a’(一个descriptor)的__set__方法。如果不注释掉第18到第20行,输出如下
Widget __setattr__ a 0
Widget __setattr__ a 123
可以看到,优先调用Widget 的__setattr__方法。因此:对于属性赋值,obj = Clz(), 那么obj.attr = var,按照这样的顺序:
(1)如果Clz定义了__setattr__方法,那么调用该方法,否则
(2)如果“attr”是出现在Clz或其基类的__dict__中, 且attr是data descriptor, 那么调用其__set__方法, 否则
(3)等价调用obj.__dict__['attr'] = var
references
python属性查找 深入理解(attribute lookup)的更多相关文章
- python属性查找(attribute lookup)
在Python中,属性查找(attribute lookup)是比较复杂的,特别是涉及到描述符descriptor的时候. 在上一文章末尾,给出了一段代码,就涉及到descriptor与att ...
- python属性查找
python中执行obj.attr时,将调用特殊方法obj.__getattribute__('attr'),该方法执行搜索来查找该属性,通常涉及检查特性.查找实例字典.查找类字典以及搜索基类.如果搜 ...
- python 二分法查找思考理解小白向け
首先说一下二分法查找的思路.这是面向小白的课程,大佬请让步谢谢 给定一个有序的序列(必须是排好序的)例如[1,2,3,4,5,6,7,8,9,10,20,30,400],然后我们查询一个元素出现的坐标 ...
- python描述符和属性查找
python描述符 定义 一般说来,描述符是一种访问对象属性时候的绑定行为,如果这个对象属性定义了__get__(),__set__(), and __delete__()一种或者几种,那么就称之为描 ...
- 非常易于理解‘类'与'对象’ 间 属性 引用关系,暨《Python 中的引用和类属性的初步理解》读后感
关键字:名称,名称空间,引用,指针,指针类型的指针(即指向指针的指针) 我读完后的理解总结: 1. 我们知道,python中的变量的赋值操作,变量其实就是一个名称name,赋值就是将name引用到一个 ...
- python 3 属性查找与绑定方法
1.属性查找 类有两种属性:数据属性和函数属性 (1)类的数据属性是所有对象共享的 #类的数据属性是所有对象共享的,id都一样 class OldboyStudent: school='oldboy' ...
- python基础语法20 面向对象5 exec内置函数的补充,元类,属性查找顺序
exec内置函数的补充 exec: 是一个python内置函数,可以将字符串的代码添加到名称空间中; - 全局名称空间 - 局部名称空间 exec(字符串形式的代码, 全局名称空间, 局部名称空间) ...
- Python属性的查找顺序
属性查找顺序 关于属性描述符请看上文>属性描述符 在梳理属性查找相关知识时,查看了很多的书籍和他人的博客,发现很多讲的过于抽象,并没有一个清晰的流程呈现.特此写下我对于此方面的理解和总结. ...
- python之属性描述符与属性查找规则
描述符 import numbers class IntgerField: def __get__(self, isinstance, owner): print('获取age') return se ...
随机推荐
- ps -ef|grep ?解释
上述内容为: 命令拆解: ps:将某个进程显示出来-A 显示所有程序. -e 此参数的效果和指定"A"参数相同.-f 显示UID,PPIP,C与STIME栏位. grep命令是查找 ...
- PyCharm实现高效远程调试代码
PyCharm实现高效远程调试代码 (薛刚强) 为方便Python代码学习和项目开发,目前选择专业的 IDE 开发工具 ,如 PyCham.针对个人使用的技巧做个笔记,分享给大家,有描述 ...
- 安卓开发-设置RadioButton的点击效果
在安卓开发中用到底部菜单栏 需要用到RadioButton这个组件 实际应用的过程中,需要对按钮进行点击,为了让用户知道是否点击可这个按钮,可以设置点击后 ,该按钮的颜色或者背景发生变化. layou ...
- (网页)JS编程中,有时需要在一个方法返回两个个或两个以上的数据
转自脚本之家: 1 使用数组的方式,如下: <html> <head> <title>JS函数返回多个值</title> </head> & ...
- python第七十一天---堡垒机
堡垒机的表结构图:
- sql 查询表格中多列重复的数据并显示该表的其他列
我们一般情况下通过分组函数group by来查询重复的列 ) R 但是查询出的结果不能显示该表的其他列 想要查询一张表中有多个列重复的数据且也要显示该表的其他列 SELECT M.* FROM [db ...
- check
private int AddNewstoDB(NewsModels newsModels, string dateTimeStr, string jsonStr, string cid, strin ...
- Hybris 用户注册的时候何如添加指定的用户组
主要逻辑:xxx.service.impl.ConsultantServiceImpl public class ConsultantServiceImpl extends DefaultCustom ...
- python 计时累积超过24小时时继续往上累加
最近在做一个工具,要求在工具上面加上程序运行的时间,所以做了个计时器 在网上找了很多发现都是24小时制的,超过24小时后就会回0 然后自己根据24小时制修改了一个不停累加时间的 若是想超过24小时后以 ...
- Linux 用户名、主机添加背景色
文章参考:PS1应用之——修改linux终端命令行各字体颜色 Linux 用户名.主机添加背景色,用于生产环境,这样可以减少人为的误操作. [root@zhang ~]# tail /etc/bash ...