Python中几种属性访问的区别与用法详解


Posted in Python onOctober 10, 2018

起步

在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作。例如,有一个类实例对象foo,它有一个name属性,那便可以使用foo.name对此属性进行访问。一般而言,点(.)属性运算符比较直观,也是我们经常碰到的一种属性访问方式。

python的提供一系列和属性访问有关的特殊方法: __get__ , __getattr__ , __getattribute__ , __getitem__ 。本文阐述它们的区别和用法。

属性的访问机制

一般情况下,属性访问的默认行为是从对象的字典中获取,并当获取不到时会沿着一定的查找链进行查找。例如 a.x 的查找链就是,从 a.__dict__['x'] ,然后是 type(a).__dict__['x'] ,再通过 type(a) 的基类开始查找。

若查找链都获取不到属性,则抛出 AttributeError 异常。

__getattr__ 方法

__getattr__函数的作用: 如果属性查找(attribute lookup)在实例以及对应的类中(通过__dict__)失败, 那么会调用到类的__getattr__函数, 如果没有定义这个函数,那么抛出AttributeError异常。由此可见,__getattr__一定是作用于属性查找的最后一步,兜底。

这个方法是当对象的属性不存在是调用。如果通过正常的机制能找到对象属性的话,不会调用 __getattr__ 方法。

class A:
 a = 1
 def __getattr__(self, item):
 print('__getattr__ call')
 return item

t = A()
print(t.a)
print(t.b)
# output
1
__getattr__ call
b

__getattribute__ 方法

这个方法会被无条件调用。不管属性存不存在。如果类中还定义了 __getattr__ ,则不会调用 __getattr__() 方法,除非在 __getattribute__ 方法中显示调用 __getattr__() 或者抛出了 AttributeError 。

class A:
 a = 1
 def __getattribute__(self, item):
 print('__getattribute__ call')
 raise AttributeError

 def __getattr__(self, item):
 print('__getattr__ call')
 return item

t = A()
print(t.a)
print(t.b)

所以一般情况下,为了保留 __getattr__ 的作用, __getattribute__() 方法中一般返回父类的同名方法:

def __getattribute__(self, item):
 return object.__getattribute__(self, item)

使用基类的方法来获取属性能避免在方法中出现无限递归的情况。

__get__ 方法

这个方法比较简单说明,它与前面的关系不大。

如果一个类中定义了 __get__() , __set__() 或 __delete__() 中的任何方法。则这个类的对象称为描述符。

class Descri(object):
 def __get__(self, obj, type=None):
 print("call get")

 def __set__(self, obj, value):
 print("call set")

class A(object):
 x = Descri()

a = A()
a.__dict__['x'] = 1 # 不会调用 __get__
a.x  # 调用 __get__

如果查找的属性是在描述符对象中,则这个描述符会覆盖上文说的属性访问机制,体现在查找链的不同,而这个行文也会因为调用的不同而稍有不一样:

  • 如果调用是对象实例(题目中的调用方式), a.x 则转换为调用: 。 type(a).__dict__['x'].__get__(a, type(a))
  • 如果调用的是类属性, A.x 则转换为: A.__dict__['x'].__get__(None, A)
  • 其他情况见文末参考资料的文档

__getitem__ 方法

这个调用也属于无条件调用,这点与 __getattribute__ 一致。区别在于 __getitem__ 让类实例允许 [] 运算,可以这样理解:

  • __getattribute__ 适用于所有 . 运算符;
  • __getitem__ 适用于所有 [] 运算符。
class A(object):
 a = 1

 def __getitem__(self, item):
 print('__getitem__ call')
 return item

t = A()
print(t['a'])
print(t['b'])

如果仅仅想要对象能够通过 [] 获取对象属性可以简单的:

def __getitem(self, item):
 return object.__getattribute__(self, item)

总结

当这几个方法同时出现可能就会扰乱你了。我在网上看到一份示例还不错,稍微改了下:

class C(object):
 a = 'abc'

 def __getattribute__(self, *args, **kwargs):
 print("__getattribute__() is called")
 return object.__getattribute__(self, *args, **kwargs)

 # return "haha"
 def __getattr__(self, name):
 print("__getattr__() is called ")
 return name + " from getattr"

 def __get__(self, instance, owner):
 print("__get__() is called", instance, owner)
 return self

 def __getitem__(self, item):
 print('__getitem__ call')
 return object.__getattribute__(self, item)

 def foo(self, x):
 print(x)

class C2(object):
 d = C()

if __name__ == '__main__':
 c = C()
 c2 = C2()
 print(c.a)
 print(c.zzzzzzzz)
 c2.d
 print(c2.d.a)
 print(c['a'])

可以结合输出慢慢理解,这里还没涉及继承关系呢。总之,每个以 __ get 为前缀的方法都是获取对象内部数据的钩子,名称不一样,用途也存在较大的差异,只有在实践中理解它们,才能真正掌握它们的用法。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python中一些自然语言工具的使用的入门教程
Apr 13 Python
利用Django内置的认证视图实现用户密码重置功能详解
Nov 24 Python
python实现简单遗传算法
Mar 19 Python
python面向对象多线程爬虫爬取搜狐页面的实例代码
May 31 Python
使用Python编写Prometheus监控的方法
Oct 15 Python
利用python实现在微信群刷屏的方法
Feb 21 Python
Python进阶之@property动态属性的实现
Apr 01 Python
Python3实现从排序数组中删除重复项算法分析
Apr 03 Python
Python缓存技术实现过程详解
Sep 25 Python
Pycharm小白级简单使用教程
Jan 08 Python
使用python求解二次规划的问题
Feb 29 Python
python代码实现将列表中重复元素之间的内容全部滤除
May 22 Python
Python的argparse库使用详解
Oct 09 #Python
详解Python3中的迭代器和生成器及其区别
Oct 09 #Python
不知道这5种下划线的含义,你就不算真的会Python!
Oct 09 #Python
详解利用django中间件django.middleware.csrf.CsrfViewMiddleware防止csrf攻击
Oct 09 #Python
详解如何将python3.6软件的py文件打包成exe程序
Oct 09 #Python
让代码变得更易维护的7个Python库
Oct 09 #Python
windows下cx_Freeze生成Python可执行程序的详细步骤
Oct 09 #Python
You might like
php rsa 加密,解密,签名,验签详解
2016/12/06 PHP
PHP 7.4中使用预加载的方法详解
2019/07/08 PHP
js数据验证集合、js email验证、js url验证、js长度验证、js数字验证等简单封装
2010/05/15 Javascript
Jquery下判断Id是否存在的代码
2011/01/06 Javascript
JQuery入门——用bind方法绑定事件处理函数应用介绍
2013/02/05 Javascript
JavaScript异步编程:异步数据收集的具体方法
2013/08/19 Javascript
jQuery封装的获取Url中的Get参数示例
2013/11/26 Javascript
用js控制组织结构图可以任意拖拽到指定位置
2014/01/17 Javascript
JavaScript立即执行函数的三种不同写法
2014/09/05 Javascript
js判断是否按下了Shift键的方法
2015/01/27 Javascript
jquery带下拉菜单和焦点图代码分享
2015/08/24 Javascript
详解AngularJS中的http拦截
2016/02/09 Javascript
使用Jasmine和Karma对AngularJS页面程序进行测试
2016/03/05 Javascript
简单实现js选项卡切换效果
2017/02/09 Javascript
JavaScript数据结构学习之数组、栈与队列
2017/05/02 Javascript
vue 设置proxyTable参数进行代理跨域
2018/04/09 Javascript
解决Layui数据表格中checkbox位置不居中的方法
2018/08/15 Javascript
javascript实现小型区块链功能
2019/04/03 Javascript
JavaScript利用键盘码控制div移动
2020/03/19 Javascript
javascript设计模式 ? 命令模式原理与用法实例分析
2020/04/20 Javascript
vue自动添加浏览器兼容前后缀操作
2020/08/13 Javascript
jQuery实现tab栏切换效果
2020/12/22 jQuery
JS canvas实现画板和签字板功能
2021/02/23 Javascript
[03:55]DOTA2完美大师赛选手传记——LFY.MONET
2017/11/18 DOTA
[48:29]2018DOTA2亚洲邀请赛3月30日 小组赛A组 LGD VS KG
2018/03/31 DOTA
[01:04]不如跳舞!DOTA2新英雄玛尔斯的欢乐日常
2019/03/11 DOTA
[50:54]完美世界DOTA2联赛 GXR vs IO 第三场 11.07
2020/11/10 DOTA
python连接PostgreSQL数据库的过程详解
2019/09/18 Python
Django后台管理系统的图文使用教学
2020/01/20 Python
获取python运行输出的数据并解析存为dataFrame实例
2020/07/07 Python
澳大利亚宠物商店:Petbarn
2017/11/18 全球购物
在线学习西班牙语、法语或其他语言:Babbel.com
2018/02/07 全球购物
高三英语教学反思
2014/01/13 职场文书
2014领导班子四风问题对照检查材料思想汇报
2014/09/21 职场文书
2016年班主任培训心得体会
2016/01/07 职场文书
利用 Python 的 Pandas和 NumPy 库来清理数据
2022/04/13 Python