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服务器与android客户端socket通信实例
Nov 12 Python
使用C语言扩展Python程序的简单入门指引
Apr 14 Python
深入解析Python中的变量和赋值运算符
Oct 12 Python
Python中进程和线程的区别详解
Oct 29 Python
Python利用openpyxl库遍历Sheet的实例
May 03 Python
Django集成搜索引擎Elasticserach的方法示例
Jun 04 Python
Django 源码WSGI剖析过程详解
Aug 05 Python
win10安装tesserocr配置 Python使用tesserocr识别字母数字验证码
Jan 16 Python
Python反爬虫伪装浏览器进行爬虫
Feb 28 Python
Keras 在fit_generator训练方式中加入图像random_crop操作
Jul 03 Python
python爬虫实现爬取同一个网站的多页数据的实例讲解
Jan 18 Python
Python Pandas知识点之缺失值处理详解
May 11 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
全国FM电台频率大全 - 15 山东省
2020/03/11 无线电
php中3种方法统计字符串中每种字符的个数并排序
2012/08/27 PHP
解决PhpMyAdmin中导入2M以上大文件限制的方法分享
2014/06/06 PHP
PHP临时文件的安全性分析
2014/07/04 PHP
PHP+shell脚本操作Memcached和Apache Status的实例分享
2016/03/11 PHP
PHP模板引擎Smarty内建函数section,sectionelse用法详解
2016/04/11 PHP
腾讯与新浪的通过IP地址获取当前地理位置(省份)的接口
2010/07/26 Javascript
动态加载图片路径 保持JavaScript控件的相对独立性
2010/09/03 Javascript
node.js中的fs.readlinkSync方法使用说明
2014/12/17 Javascript
javascript跑马灯抽奖实例讲解
2020/04/17 Javascript
详解React-Todos入门例子
2016/11/08 Javascript
JS简单实现表格排序功能示例
2016/12/20 Javascript
微信小程序开发教程-手势解锁实例
2017/01/06 Javascript
微信小程序中实现一对多发消息详解及实例代码
2017/02/14 Javascript
原生JS中slice()方法和splice()区别
2017/03/06 Javascript
使用ESLint禁止项目导入特定模块的方法步骤
2019/03/04 Javascript
layui监听下拉选框选中值变化的方法(包含监听普通下拉选框)
2019/09/24 Javascript
使用Vue-cli3.0创建的项目 如何发布npm包
2019/10/10 Javascript
基于ajax及jQuery实现局部刷新过程解析
2020/09/12 jQuery
vuex实现购物车功能
2020/06/28 Javascript
Python实现过滤单个Android程序日志脚本分享
2015/01/16 Python
Python新手在作用域方面经常容易碰到的问题
2015/04/03 Python
Python中用Spark模块的使用教程
2015/04/13 Python
使用Python的Django框架实现事务交易管理的教程
2015/04/20 Python
Python实现生成随机数据插入mysql数据库的方法
2017/12/25 Python
运用PyTorch动手搭建一个共享单车预测器
2019/08/06 Python
idea2020手动安装python插件的实现方法
2020/07/17 Python
canvas学习和滤镜实现代码
2018/08/22 HTML / CSS
匡威意大利官方商店 :Converse意大利
2018/11/27 全球购物
工商管理实习生自我鉴定范文
2013/12/18 职场文书
烹饪大赛策划方案
2014/05/26 职场文书
2014年中职班主任工作总结
2014/12/16 职场文书
2015年大学组织委员个人工作总结
2015/10/23 职场文书
社区志愿者服务心得体会
2016/01/22 职场文书
2016年最美孝心少年事迹材料
2016/02/26 职场文书
解决MultipartFile.transferTo(dest) 报FileNotFoundExcep的问题
2021/07/01 Java/Android