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 相关文章推荐
Python类的动态修改的实例方法
Mar 24 Python
Python 中urls.py:URL dispatcher(路由配置文件)详解
Mar 24 Python
Python实现八皇后问题示例代码
Dec 09 Python
Python3中_(下划线)和__(双下划线)的用途和区别
Apr 26 Python
PyQt5实现让QScrollArea支持鼠标拖动的操作方法
Jun 19 Python
Python使用线程来接收串口数据的示例
Jul 02 Python
使用Pyhton集合set()实现成果查漏的例子
Nov 24 Python
使用Matplotlib 绘制精美的数学图形例子
Dec 13 Python
Python中的xlrd模块使用原理解析
May 21 Python
简单的Python人脸识别系统
Jul 14 Python
pandas将list数据拆分成行或列的实现
Dec 13 Python
python使用pygame创建精灵Sprite
Apr 06 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
PHP下判断网址是否有效的代码
2011/10/08 PHP
怎样使用php与jquery设置和读取cookies
2013/08/08 PHP
php网站被挂木马后的修复方法总结
2014/11/06 PHP
PHP多线程之内部多线程实例分析
2015/03/09 PHP
使用php转义输出HTML到JavaScript
2015/03/27 PHP
使用PHPCMS搭建wap手机网站
2015/09/20 PHP
PHP读取CSV大文件导入数据库的实例
2017/07/24 PHP
使用javascript过滤html的字符串(注释标记法)
2013/07/08 Javascript
页面载入结束自动调用js函数示例
2013/09/23 Javascript
jQuery获取选中内容及设置元素属性的方法
2014/07/09 Javascript
JavaScript fontsize方法入门实例(按照指定的尺寸来显示字符串)
2014/10/17 Javascript
基于jquery的手风琴图片展示效果实现方法
2014/12/16 Javascript
深入理解JavaScript系列(49):Function模式(上篇)
2015/03/04 Javascript
JQuery遍历DOM节点的方法
2015/06/11 Javascript
情人节单身的我是如何在敲完代码之后收到12束玫瑰的(javascript)
2015/08/21 Javascript
详解JavaScript操作HTML DOM的基本方式
2015/10/21 Javascript
Bootstrap中表单控件状态(验证状态)
2016/08/04 Javascript
angularJs 表格添加删除修改查询方法
2018/02/27 Javascript
7个好用的JavaScript技巧分享(译)
2019/05/07 Javascript
Element Rate 评分的使用方法
2020/07/27 Javascript
举例详解Python中循环语句的嵌套使用
2015/05/14 Python
详解Python中的日志模块logging
2015/06/19 Python
Python使用Selenium模块实现模拟浏览器抓取淘宝商品美食信息功能示例
2018/07/18 Python
Django中如何防范CSRF跨站点请求伪造攻击的实现
2019/04/28 Python
详解CSS透明opacity和IE各版本透明度滤镜filter的最准确用法
2016/12/20 HTML / CSS
贝尔帐篷精品店:Bell Tent Boutique
2019/06/12 全球购物
Agoda中文官网:安可达(低价预订全球酒店)
2021/01/18 全球购物
在校生汽车维修实习自我鉴定
2013/09/19 职场文书
中文系师范生自荐信
2013/10/01 职场文书
社区党建工作汇报材料
2014/08/14 职场文书
财务会计实训报告
2014/11/05 职场文书
小学体育课教学反思
2016/02/16 职场文书
教师学期述职自我鉴定
2019/08/16 职场文书
PHP判断是否是json字符串
2021/04/01 PHP
「月刊Comic Alive」2022年5月号封面公开
2022/03/21 日漫
【海涛教你打DOTA】虚空假面第一视角骨弓3房29杀
2022/04/01 DOTA