Python获取对象属性的几种方式小结


Posted in Python onMarch 12, 2020

本文将简单介绍四种获取对象的方法。

假如有以下的类:

class Person(object):
 def __init__(self, name, age):
  self.name = name
  self.age = age
  
 def __str__(self):
  return 'name=%s, age=%s' % (self.name, self.age)

方法一:使用属性运算符

print(xmr.name)

方法二:通过属性字典__dict__

print(xmr.__dict__['name'])

方法三:通过getattr函数

print(getattr(xmr, 'name'))

方法四:operator.attrgetter

import operator
 
op = operator.attrgetter('name')
print(op(xmr))

方法四可以用于对象的排序,比如需要根据年龄age来排序Person对象:

import operator
 
p_list = [Person('xiemanR', 18), Person('zhangshan', 17), Person('lisi', 20), Person('wangwu', 25)]
 
r = sorted(p_list, key=operator.attrgetter('age'))
 
for i in r:
 print(i)

输出结果:

Person(name=zhangshan, age=17)
Person(name=xiemanR, age=18)
Person(name=lisi, age=20)
Person(name=wangwu, age=25)

PS:其实第四种方法是调用第三种方法的,只是简单封装了一下,我们看看operator.attrgetter实现就知道了:

class attrgetter:
 def __init__(self, attr, *attrs):
  if not attrs:
   if not isinstance(attr, str):
    raise TypeError('attribute name must be a string')
   names = attr.split('.')
   def func(obj):
    for name in names:
     obj = getattr(obj, name)
    return obj
   self._call = func
  else:
   getters = tuple(map(attrgetter, (attr,) + attrs))
   def func(obj):
    return tuple(getter(obj) for getter in getters)
   self._call = func
 
 def __call__(self, obj):
  return self._call(obj)

完。

补充知识:深入理解python对象及属性

类属性和实例属性

首先来看看类属性和类实例的属性在python中如何存储,通过__dir__方法来查看对象的属性

>>> class Test(object):
    pass
>>> test = Test()
# 查看类属性
>>> dir(Test)
['__class__','__delattr__','__dict__','__doc__','__format__',
'__getattribute__', '__hash__', '__init__', '__module__',
 '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
 '__weakref__']
# 查看实例属性
>>> dir(test)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', 
'__getattribute__', '__hash__', '__init__', '__module__',
 '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 
 '__weakref__']

我们主要看一个属性__dict__,因为 __dict__保存的对象的属性,看下面一个例子

>>> class Spring(object):
...   season = "the spring of class"
... 

# 查看Spring类保存的属性
>>> Spring.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Spring' objects>, 
'season': 'the spring of class', 
'__module__': '__main__', 
'__weakref__': <attribute '__weakref__' of 'Spring' objects>, 
'__doc__': None})

# 通过两种方法访问类属性
>>> Spring.__dict__['season']
'the spring of class'
>>> Spring.season
'the spring of class'

发现__dict__有个'season'键,这就是这个类的属性,其值就是类属性的数据.

接来看,看看它的实例属性

>>> s = Spring()
# 实例属性的__dict__是空的
>>> s.__dict__
{}
# 其实是指向的类属性
>>> s.season
'the spring of class'

# 建立实例属性
>>> s.season = "the spring of instance"
# 这样,实例属性里面就不空了。这时候建立的实例属性和类属性重名,并且把它覆盖了
>>> s.__dict__
{'season': 'the spring of instance'}
>>> s.__dict__['season']
'the spring of instance'
>>> s.season
'the spring of instance'

# 类属性没有受到实例属性的影响
>>> Spring.__dict__['season']
'the spring of class'
>>> Spring.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Spring' objects>, 'season': 'the spring of class', '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Spring' objects>, '__doc__': None})

# 如果将实例属性删除,又会调用类属性
>>> del s.season
>>> s.__dict__
{}
>>> s.season
'the spring of class'

# 自定义实例属性,对类属性没有影响
>>> s.lang = "python"
>>> s.__dict__
{'lang': 'python'}
>>> s.__dict__['lang']
'python'

# 修改类属性
>>> Spring.flower = "peach"
>>> Spring.__dict__
dict_proxy({'__module__': '__main__', 
'flower': 'peach', 
'season': 'the spring of class', 
'__dict__': <attribute '__dict__' of 'Spring' objects>, '__weakref__': <attribute '__weakref__' of 'Spring' objects>, '__doc__': None})
>>> Spring.__dict__['flower']
'peach'
# 实例中的__dict__并没有变化
>>> s.__dict__
{'lang': 'python'}
# 实例中找不到flower属性,调用类属性
>>> s.flower
'peach'

下面看看类中包含方法,__dict__如何发生变化

# 定义类
>>> class Spring(object):
...   def tree(self, x):
...     self.x = x
...     return self.x
... 
# 方法tree在__dict__里面
>>> Spring.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Spring' objects>, 
'__weakref__': <attribute '__weakref__' of 'Spring' objects>, 
'__module__': '__main__', 
'tree': <function tree at 0xb748fdf4>, 
'__doc__': None})
>>> Spring.__dict__['tree']
<function tree at 0xb748fdf4>

# 建立实例,但是__dict__中没有方法 
>>> t = Spring()
>>> t.__dict__
{}

# 执行方法
>>> t.tree("xiangzhangshu")
'xiangzhangshu'
# 实例方法(t.tree('xiangzhangshu'))的第一个参数(self,但没有写出来)绑定实例 t,透过 self.x 来设定值,即给 t.__dict__添加属性值。
>>> t.__dict__
{'x': 'xiangzhangshu'}
# 如果没有将x 赋值给 self 的属性,而是直接 return,结果发生了变化
>>> class Spring(object):
...   def tree(self, x):
...     return x
>>> s = Spring()
>>> s.tree("liushu")
'liushu'
>>> s.__dict__
{}

需要理解python中的一个观点,一切都是对象,不管是类还是实例,都可以看成是对象,符合object.attribute ,都会有自己的属性

使用__slots__优化内存使用

默认情况下,python在各个实例中为名为__dict__的字典里存储实例属性,而字典会消耗大量内存(字典要使用底层散列表提升访问速度), 通过__slots__类属性,在元组中存储实例属性,不用字典,从而节省大量内存

# 在类中定义__slots__属性就是说这个类中所有实例的属性都在这儿了,如果几百万个实例同时活动,能节省大量内存
>>> class Spring(object):
...   __slots__ = ("tree", "flower")
... 
# 仔细看看 dir() 的结果,还有__dict__属性吗?没有了,的确没有了。也就是说__slots__把__dict__挤出去了,它进入了类的属性。
>>> dir(Spring)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'flower', 'tree']
>>> Spring.__slots__
('tree', 'flower')
# 实例化
>>> t = Spring()
>>> t.__slots__
('tree', 'flower')

# 通过类赋予属性值
>>> Spring.tree = "liushu"
# tree这个属性是只读的, 实例不能修改
>>> t.tree = "guangyulan"
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'Spring' object attribute 'tree' is read-only
>>> t.tree
'liushu'

# 对于用类属性赋值的属性,只能用来修改
>>> Spring.tree = "guangyulan"
>>> t.tree
'guangyulan'

# 对于没有用类属性赋值的属性,可以通过实例来修改
>>> t.flower = "haitanghua"
>>> t.flower
'haitanghua'
# 实例属性的值并没有传回到类属性,你也可以理解为新建立了一个同名的实例属性
>>> Spring.flower
<member 'flower' of 'Spring' objects>
# 如果再给类属性赋值
>>> Spring.flower = "ziteng"
>>> t.flower
'ziteng'

如果使用的当,__slots__可以显著节省内存,按需要注意一下问题

在类中定义__slots__之后,实例不能再有__slots__所列名称之外的其他属性

每个子类都要定义__slots__熟悉,因为解释器会忽略继承__slots__属性

如果不把__werkref__加入__slots__,实例不能作为弱引用的目标

属性的魔术方法

来看几个魔术方法

__setattr__(self,name,value):如果要给 name 赋值,就调用这个方法。
__getattr__(self,name):如果 name 被访问,同时它不存在的时候,此方法被调用。
__getattribute__(self,name):当 name被访问时自动被调用(注意:这个仅能用于新式类),无论 name 是否存在,都要被调用。
__delattr__(self,name):如果要删除 name,这个方法就被调用。
>>> class A(object):
...   def __getattr__(self, name):
...     print "You use getattr"
...   def __setattr__(self, name, value):
...     print "You use setattr"
...     self.__dict__[name] = value
# a.x,按照本节开头的例子,是要报错的。但是,由于在这里使用了__getattr__(self, name) 方法,当发现 x 不存在于对象的__dict__中的时候,就调用了__getattr__,即所谓“拦截成员”。
>>> a = A()
>>> a.x
You use getattr

# 给对象的属性赋值时候,调用了__setattr__(self, name, value)方法,这个方法中有一句 self.__dict__[name] = value,通过这个语句,就将属性和数据保存到了对象的__dict__中
>>> a.x = 7
You use setattr

# 测试__getattribute__(self,name)
>>> class B(object):
...   def __getattribute__(self, name):
...     print "you are useing getattribute"
...     return object.__getattribute__(self, name)
# 返回的内容用的是 return object.__getattribute__(self, name),而没有使用 return self.__dict__[name]。因为如果用这样的方式,就是访问 self.__dict__,只要访问这个属性,就要调用`getattribute``,这样就导致了无限递归

# 访问不存在的成员,可以看到,已经被__getattribute__拦截了,虽然最后还是要报错的。
>>> b = B()
>>> b.y
you are useing getattribute
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "<stdin>", line 4, in __getattribute__
AttributeError: 'B' object has no attribute 'y'

Property函数

porperty可以作为装饰器使用把方法标记为特性

class Vector(object):
  def __init__(self, x, y):
    # 使用两个前导下划线,把属性标记为私有
    self.__x = float(x)
    self.__y = float(y)
  
  # porperty装饰器把读值方法标记为特性
  @property
  def x(self):
    return self.__x
    
  @property
  def y(self):
    return self.__y
    
vector = Vector(3,4)
print(vector.x, vector.y)

使用property可以将函数封装为属性

class Rectangle(object):
  """
  the width and length of Rectangle
  """
  def __init__(self):
    self.width = 0
    self.length = 0

  def setSize(self, size):
    self.width, self.length = size
  def getSize(self):
    return self.width, self.length

if __name__ == "__main__":
  r = Rectangle()
  r.width = 3
  r.length = 4
  print r.getSize()  # (3,4)
  r.setSize( (30, 40) )
  print r.width  # 30
  print r.length  # 40

这段代码可以正常运行,但是属性的调用方式可以改进,如下:

class Rectangle(object):
  """
  the width and length of Rectangle
  """
  def __init__(self):
    self.width = 0
    self.length = 0

  def setSize(self, size):
    self.width, self.length = size
  def getSize(self):
    return self.width, self.length
  # 使用property方法将函数封装为属性,更优雅
  size = property(getSize, setSize)

if __name__ == "__main__":
  r = Rectangle()
  r.width = 3
  r.length = 4
  print r.size   # (30, 40)
  r.size = 30, 40
  print r.width  # 30
  print r.length  # 40

使用魔术方法实现:

class NewRectangle(object):
  def __init__(self):
    self.width = 0
    self.length = 0
  
  def __setattr__(self, name, value):
    if name == 'size':
      self.width, self, length = value
    else:
      self.__dict__[name] = value
      
  def __getattr__(self, name):
    if name == 'size':
      return self.width, self.length
    else:
      raise AttrubuteErrir
      
if __name__ == "__main__":
  r = Rectangle()
  r.width = 3
  r.length = 4
  print r.size   # (30, 40)
  r.size = 30, 40
  print r.width  # 30
  print r.length  # 40

属性的获取顺序

最后我们来看看熟悉的获得顺序:通过实例获取其属性,如果在__dict__中有相应的属性,就直接返回其结果;如果没有,会到类属性中找。

看下面一个例子:

class A(object):
  author = "qiwsir"
  def __getattr__(self, name):
    if name != "author":
      return "from starter to master."

if __name__ == "__main__":
  a = A()
  print a.author # qiwsir
  print a.lang # from starter to master.

当 a = A() 后,并没有为实例建立任何属性,或者说实例的__dict__是空的。但是如果要查看 a.author,因为实例的属性中没有,所以就去类属性中找,发现果然有,于是返回其值 “qiwsir”。但是,在找 a.lang的时候,不仅实例属性中没有,类属性中也没有,于是就调用了__getattr__()方法。在上面的类中,有这个方法,如果没有__getattr__()方法呢?如果没有定义这个方法,就会引发 AttributeError,这在前面已经看到了。

以上这篇Python获取对象属性的几种方式小结就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Tensorflow 自带可视化Tensorboard使用方法(附项目代码)
Feb 10 Python
python使用Pycharm创建一个Django项目
Mar 05 Python
python2.7无法使用pip的解决方法(安装easy_install)
Apr 03 Python
pandas的object对象转时间对象的方法
Apr 11 Python
Python实现接受任意个数参数的函数方法
Apr 21 Python
Python数据结构与算法(几种排序)小结
Jun 22 Python
python将logging模块封装成单独模块并实现动态切换Level方式
May 12 Python
Pycharm无法打开双击没反应的问题及解决方案
Aug 17 Python
python opencv角点检测连线功能的实现代码
Nov 24 Python
python解决OpenCV在读取显示图片的时候闪退的问题
Feb 23 Python
python编程学习使用管道Pipe编写优化代码
Nov 20 Python
Pygame游戏开发之太空射击实战敌人精灵篇
Aug 05 Python
深入浅析Python 命令行模块 Click
Mar 11 #Python
python字典和json.dumps()的遇到的坑分析
Mar 11 #Python
解决pyecharts运行后产生的html文件用浏览器打开空白
Mar 11 #Python
在django admin详情表单显示中添加自定义控件的实现
Mar 11 #Python
django admin 添加自定义链接方式
Mar 11 #Python
django xadmin 管理器常用显示设置方式
Mar 11 #Python
django从后台返回html代码的实例
Mar 11 #Python
You might like
星际争霸, 教主第一视角, ZvT经典龙蛇演义
2020/03/02 星际争霸
PHP与MySQL开发中页面出现乱码的一种解决方法
2007/07/29 PHP
谈谈关于php的优点与缺点
2013/04/11 PHP
php pdo oracle中文乱码的快速解决方法
2016/05/16 PHP
Yii2框架dropDownList下拉菜单用法实例分析
2016/07/18 PHP
php抽象方法和抽象类实例分析
2016/12/07 PHP
windows 2008r2+php5.6.28环境搭建详细过程
2019/06/18 PHP
js停止输出代码
2008/07/20 Javascript
jquery ready()的几种实现方法小结
2010/06/18 Javascript
JS图片浏览组件PhotoLook的公开属性方法介绍和进阶实例代码
2010/11/09 Javascript
ExtJS中文乱码之GBK格式编码解决方案及代码
2013/01/20 Javascript
网页前端优化之滚动延时加载图片示例
2013/07/13 Javascript
JavaScript中对循环语句的优化技巧深入探讨
2014/06/06 Javascript
JavaScript节点及列表操作实例小结
2015/08/05 Javascript
使用pcs api往免费的百度网盘上传下载文件的方法
2016/03/17 Javascript
使用 bootstrap modal遇到的问题小结
2016/11/09 Javascript
jquery-mobile表单的创建方法详解
2016/11/23 Javascript
如何利用JQuery实现从底部回到顶部的功能
2016/12/27 Javascript
Vue响应式原理深入解析及注意事项
2017/12/11 Javascript
解决vue this.$forceUpdate() 处理页面刷新问题(v-for循环值刷新等)
2018/07/26 Javascript
JavaScript实现邮箱后缀提示功能的示例代码
2018/12/13 Javascript
vuex actions异步修改状态的实例详解
2019/11/06 Javascript
Python随机生成一个6位的验证码代码分享
2015/03/24 Python
python使用PIL模块获取图片像素点的方法
2019/01/08 Python
树莓派采用socket方式文件传输(python)
2019/06/22 Python
Django stark组件使用及原理详解
2019/08/22 Python
python如何使用腾讯云发送短信
2020/09/17 Python
Django修改app名称和数据表迁移方案实现
2020/09/17 Python
CSS3网格的三个新特性详解
2014/04/04 HTML / CSS
canvas简易绘图的实现(海绵宝宝篇)
2018/07/04 HTML / CSS
一文彻底解决HTML5页面中长按保存图片功能
2019/06/10 HTML / CSS
如何填写个人简历自我评价
2013/12/10 职场文书
关于雷锋的演讲稿
2014/05/10 职场文书
活动宣传策划方案
2014/05/23 职场文书
公司委托书格式范文
2014/10/09 职场文书
环境卫生整治简报
2015/07/20 职场文书