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 ...
随机推荐
- 关于 ul 嵌套 li 并且再嵌套 a 的 BUG
在写网页的过程中,总是写完了这一套,样式出了问题又去找问题废了好长时间总结一下写法以下是结构 经常会出现 li 里面与文字不在一个高度上 <div class="indicators& ...
- 【代码笔记】Web-JavaScript-JavaScript 变量
一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- 【工具相关】ionic-通过nmp安装最新版本的 cordova 和 ionic
一,命令行下输入: sudo npm install -g cordova ionic 用来安装最新版本的cordova和ionic. 如下图所示: 二,等待一下,如下图所示. 三,用命令 npm u ...
- docker第一章:docker核心概念及centos6下安装
Docker三大核心概念 镜像 容器 仓库 镜像 docker镜像类似于虚拟机镜像,可以将它理解为一个面向Docker引擎的只读模板,包含了文件系统. 容器 1.容器是从镜像创建的应用运行实例,容器和 ...
- Linux 学习笔记之超详细基础linux命令 Part 3
Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 2----------------- ...
- [Linux.NET]在CentOS 7.x中编译方式安装Nginx
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行.由俄罗斯的程序设计师Igor Sysoev所开发,供俄罗斯大型的 ...
- Echarts地图展示及属性分析
Echarts,一个效果非常棒的可视化库,可以生产各种图表,动态展示,附上官方网址:http://www.echartsjs.com/index.html 之前带本科实习时有同学用过,狗哥的博客也用这 ...
- python写一个双色球彩票计算器
首先声明,赌博一定不是什么好事,也完全没有意义,不要指望用彩票发财.之所以写这个,其实是用来练手的,可以参考这个来预测一些其他的东西,意在抛砖引玉. 啰嗦完了,马上开始,先上伪代码 打开网址 读取内容 ...
- Jmeter参数化方法
用Jmeter测试时包含两种情况的参数:一种是在url中,一种是请求中需要发送的参数. 设置参数值的方法有如下几种: 一.函数助手 用Jmeter中的函数获取参数值,__Random,__thread ...
- C# 生成强命名程序集并添加到GAC
针对一些类库项目或用户控件项目(一般来说,这类项目最后编译生成的是一个或多个dll文件),在程序开发完成后,有时需要将开发的程序集(dll文件)安装部署到GAC(全局程序集缓存)中,以便其他的程序也可 ...