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中if __name__ == &quot;__main__&quot;详细解释
Oct 21 Python
Python实现求最大公约数及判断素数的方法
May 26 Python
Python的mysql数据库的更新如何实现
Jul 31 Python
VSCode Python开发环境配置的详细步骤
Feb 22 Python
python interpolate插值实例
Jul 06 Python
Python自动化xpath实现自动抢票抢货
Sep 19 Python
python之pygame模块实现飞机大战完整代码
Nov 29 Python
Django扫码抽奖平台的配置过程详解
Jan 14 Python
如何用tempfile库创建python进程中的临时文件
Jan 28 Python
写好Python代码的几条重要技巧
May 21 Python
Python 如何将integer转化为罗马数(3999以内)
Jun 05 Python
python单元测试之pytest的使用
Jun 07 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
探讨:parse url解析URL,返回其组成部分
2013/06/14 PHP
PHP实现过滤各种HTML标签
2015/05/17 PHP
php给图片加文字水印
2015/07/31 PHP
php设计模式之组合模式实例详解【星际争霸游戏案例】
2020/03/27 PHP
PHP超全局变量实现原理及代码解析
2020/09/01 PHP
jquery的$(document).ready()和onload的加载顺序
2010/05/26 Javascript
网站页面自动跳转实现方法PHP、JSP(下)
2010/08/01 Javascript
查看源码的工具 学习jQuery源码不错的工具
2011/12/26 Javascript
在javascript中关于节点内容加强
2013/04/11 Javascript
jquery插件jquery倒计时插件分享
2013/12/27 Javascript
Node.js中的缓冲与流模块详细介绍
2015/02/11 Javascript
javascript轻量级库createjs使用Easel实现拖拽效果
2016/02/19 Javascript
Bootstrap零基础学习第一课之模板
2016/07/18 Javascript
探究Vue.js 2.0新增的虚拟DOM
2016/10/20 Javascript
JavaScript实现图片懒加载(Lazyload)
2016/11/28 Javascript
如何更好的编写js async函数
2018/05/13 Javascript
JS使用正则表达式判断输入框失去焦点事件
2019/10/16 Javascript
Vue实现验证码功能
2019/12/03 Javascript
Vue是怎么渲染template内的标签内容的
2020/06/05 Javascript
Python和Java进行DES加密和解密的实例
2018/01/09 Python
详解PyTorch手写数字识别(MNIST数据集)
2019/08/16 Python
python中class的定义及使用教程
2019/09/18 Python
python 普通克里金(Kriging)法的实现
2019/12/19 Python
Python tkinter三种布局实例详解
2020/01/06 Python
英国Boots旗下太阳镜网站:Boots Designer Sunglasses
2018/07/07 全球购物
Fresh馥蕾诗英国官网:法国LVMH集团旗下高端天然护肤品牌
2018/11/01 全球购物
Myprotein意大利官网:欧洲第一运动营养品牌
2018/11/22 全球购物
全球工业:Global Industrial
2020/02/01 全球购物
活动邀请函范文
2014/01/19 职场文书
创业计划书的写作技巧及要点
2014/01/31 职场文书
ktv总经理岗位职责
2014/02/17 职场文书
婚礼答谢宴主持词
2014/03/14 职场文书
劲霸男装广告词改编版
2014/03/21 职场文书
教师专业自荐信
2014/05/31 职场文书
Spring Bean是如何初始化的详解
2022/03/22 Java/Android
Windows Server 2008配置防火墙策略详解
2022/06/28 Servers