1. 程式人生 > 程式設計 >Python獲取物件屬性的幾種方式小結

Python獲取物件屬性的幾種方式小結

本文將簡單介紹四種獲取物件的方法。

假如有以下的類:

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__','__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>,'__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','__dict__': <attribute '__dict__' 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>,'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__','__slots__','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,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,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,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>",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,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,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
  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獲取物件屬性的幾種方式小結就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。