Python對(duì)象的屬性訪問過程詳解
只想回答一個(gè)問題: 當(dāng)編譯器要讀取obj.field時(shí), 發(fā)生了什么?
看似簡單的屬性訪問, 其過程還蠻曲折的. 總共有以下幾個(gè)step:
1. 如果obj 本身(一個(gè)instance )有這個(gè)屬性, 返回. 如果沒有, 執(zhí)行 step 2
2. 如果obj 的class 有這個(gè)屬性, 返回. 如果沒有, 執(zhí)行step 3.
3. 如果在obj class 的父類有這個(gè)屬性, 返回. 如果沒有, 繼續(xù)執(zhí)行3, 直到訪問完所有的父類. 如果還是沒有, 執(zhí)行step 4.
4. 執(zhí)行obj.__getattr__方法.
通過以下代碼可以驗(yàn)證:
class A(object):
a = 'a'
class B(A):
b = 'b'
class C(B):
class_field = 'class field'
def __getattr__(self, f):
print('Method {}.__getattr__ has been called.'.format(
self.__class__.__name__))
return f
c = C()
print c.a
print c.b
print c.class_field
print c.c
輸出:
a b class field Method C.__getattr__ has been called. c
PS: python里的attribute與property不同, 當(dāng)使用了property里, property的解析優(yōu)先級(jí)最高. 詳見blog:從attribute到property.
補(bǔ)充知識(shí):深入理解python對(duì)象及屬性
類屬性和實(shí)例屬性
首先來看看類屬性和類實(shí)例的屬性在python中如何存儲(chǔ),通過__dir__方法來查看對(duì)象的屬性
>>> 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__']
# 查看實(shí)例屬性
>>> dir(test)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__',
'__getattribute__', '__hash__', '__init__', '__module__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__']
我們主要看一個(gè)屬性__dict__,因?yàn)?__dict__保存的對(duì)象的屬性,看下面一個(gè)例子
>>> 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'
發(fā)現(xiàn)__dict__有個(gè)'season'鍵,這就是這個(gè)類的屬性,其值就是類屬性的數(shù)據(jù).
接來看,看看它的實(shí)例屬性
>>> s = Spring()
# 實(shí)例屬性的__dict__是空的
>>> s.__dict__
{}
# 其實(shí)是指向的類屬性
>>> s.season
'the spring of class'
# 建立實(shí)例屬性
>>> s.season = "the spring of instance"
# 這樣,實(shí)例屬性里面就不空了。這時(shí)候建立的實(shí)例屬性和類屬性重名,并且把它覆蓋了
>>> s.__dict__
{'season': 'the spring of instance'}
>>> s.__dict__['season']
'the spring of instance'
>>> s.season
'the spring of instance'
# 類屬性沒有受到實(shí)例屬性的影響
>>> 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})
# 如果將實(shí)例屬性刪除,又會(huì)調(diào)用類屬性
>>> del s.season
>>> s.__dict__
{}
>>> s.season
'the spring of class'
# 自定義實(shí)例屬性,對(duì)類屬性沒有影響
>>> 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'
# 實(shí)例中的__dict__并沒有變化
>>> s.__dict__
{'lang': 'python'}
# 實(shí)例中找不到flower屬性,調(diào)用類屬性
>>> s.flower
'peach'
下面看看類中包含方法,__dict__如何發(fā)生變化
# 定義類
>>> 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>
# 建立實(shí)例,但是__dict__中沒有方法
>>> t = Spring()
>>> t.__dict__
{}
# 執(zhí)行方法
>>> t.tree("xiangzhangshu")
'xiangzhangshu'
# 實(shí)例方法(t.tree('xiangzhangshu'))的第一個(gè)參數(shù)(self,但沒有寫出來)綁定實(shí)例 t,透過 self.x 來設(shè)定值,即給 t.__dict__添加屬性值。
>>> t.__dict__
{'x': 'xiangzhangshu'}
# 如果沒有將x 賦值給 self 的屬性,而是直接 return,結(jié)果發(fā)生了變化
>>> class Spring(object):
... def tree(self, x):
... return x
>>> s = Spring()
>>> s.tree("liushu")
'liushu'
>>> s.__dict__
{}
需要理解python中的一個(gè)觀點(diǎn),一切都是對(duì)象,不管是類還是實(shí)例,都可以看成是對(duì)象,符合object.attribute ,都會(huì)有自己的屬性
使用__slots__優(yōu)化內(nèi)存使用
默認(rèn)情況下,python在各個(gè)實(shí)例中為名為__dict__的字典里存儲(chǔ)實(shí)例屬性,而字典會(huì)消耗大量內(nèi)存(字典要使用底層散列表提升訪問速度), 通過__slots__類屬性,在元組中存儲(chǔ)實(shí)例屬性,不用字典,從而節(jié)省大量內(nèi)存
# 在類中定義__slots__屬性就是說這個(gè)類中所有實(shí)例的屬性都在這兒了,如果幾百萬個(gè)實(shí)例同時(shí)活動(dòng),能節(jié)省大量內(nèi)存
>>> class Spring(object):
... __slots__ = ("tree", "flower")
...
# 仔細(xì)看看 dir() 的結(jié)果,還有__dict__屬性嗎?沒有了,的確沒有了。也就是說__slots__把__dict__擠出去了,它進(jìn)入了類的屬性。
>>> 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')
# 實(shí)例化
>>> t = Spring()
>>> t.__slots__
('tree', 'flower')
# 通過類賦予屬性值
>>> Spring.tree = "liushu"
# tree這個(gè)屬性是只讀的, 實(shí)例不能修改
>>> 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'
# 對(duì)于用類屬性賦值的屬性,只能用來修改
>>> Spring.tree = "guangyulan"
>>> t.tree
'guangyulan'
# 對(duì)于沒有用類屬性賦值的屬性,可以通過實(shí)例來修改
>>> t.flower = "haitanghua"
>>> t.flower
'haitanghua'
# 實(shí)例屬性的值并沒有傳回到類屬性,你也可以理解為新建立了一個(gè)同名的實(shí)例屬性
>>> Spring.flower
<member 'flower' of 'Spring' objects>
# 如果再給類屬性賦值
>>> Spring.flower = "ziteng"
>>> t.flower
'ziteng'
如果使用的當(dāng),__slots__可以顯著節(jié)省內(nèi)存,按需要注意一下問題
在類中定義__slots__之后,實(shí)例不能再有__slots__所列名稱之外的其他屬性
每個(gè)子類都要定義__slots__熟悉,因?yàn)榻忉屍鲿?huì)忽略繼承__slots__屬性
如果不把__werkref__加入__slots__,實(shí)例不能作為弱引用的目標(biāo)
屬性的魔術(shù)方法
來看幾個(gè)魔術(shù)方法
__setattr__(self,name,value):如果要給 name 賦值,就調(diào)用這個(gè)方法。 __getattr__(self,name):如果 name 被訪問,同時(shí)它不存在的時(shí)候,此方法被調(diào)用。 __getattribute__(self,name):當(dāng) name被訪問時(shí)自動(dòng)被調(diào)用(注意:這個(gè)僅能用于新式類),無論 name 是否存在,都要被調(diào)用。 __delattr__(self,name):如果要?jiǎng)h除 name,這個(gè)方法就被調(diào)用。 >>> 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,按照本節(jié)開頭的例子,是要報(bào)錯(cuò)的。但是,由于在這里使用了__getattr__(self, name) 方法,當(dāng)發(fā)現(xiàn) x 不存在于對(duì)象的__dict__中的時(shí)候,就調(diào)用了__getattr__,即所謂“攔截成員”。 >>> a = A() >>> a.x You use getattr # 給對(duì)象的屬性賦值時(shí)候,調(diào)用了__setattr__(self, name, value)方法,這個(gè)方法中有一句 self.__dict__[name] = value,通過這個(gè)語句,就將屬性和數(shù)據(jù)保存到了對(duì)象的__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) # 返回的內(nèi)容用的是 return object.__getattribute__(self, name),而沒有使用 return self.__dict__[name]。因?yàn)槿绻眠@樣的方式,就是訪問 self.__dict__,只要訪問這個(gè)屬性,就要調(diào)用`getattribute``,這樣就導(dǎo)致了無限遞歸 # 訪問不存在的成員,可以看到,已經(jīng)被__getattribute__攔截了,雖然最后還是要報(bào)錯(cuò)的。 >>> 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函數(shù)
porperty可以作為裝飾器使用把方法標(biāo)記為特性
class Vector(object):
def __init__(self, x, y):
# 使用兩個(gè)前導(dǎo)下劃線,把屬性標(biāo)記為私有
self.__x = float(x)
self.__y = float(y)
# porperty裝飾器把讀值方法標(biāo)記為特性
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
vector = Vector(3,4)
print(vector.x, vector.y)
使用property可以將函數(shù)封裝為屬性
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
這段代碼可以正常運(yùn)行,但是屬性的調(diào)用方式可以改進(jìn),如下:
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方法將函數(shù)封裝為屬性,更優(yōu)雅
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
使用魔術(shù)方法實(shí)現(xiàn):
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
屬性的獲取順序
最后我們來看看熟悉的獲得順序:通過實(shí)例獲取其屬性,如果在__dict__中有相應(yīng)的屬性,就直接返回其結(jié)果;如果沒有,會(huì)到類屬性中找。
看下面一個(gè)例子:
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.
當(dāng) a = A() 后,并沒有為實(shí)例建立任何屬性,或者說實(shí)例的__dict__是空的。但是如果要查看 a.author,因?yàn)閷?shí)例的屬性中沒有,所以就去類屬性中找,發(fā)現(xiàn)果然有,于是返回其值 “qiwsir”。但是,在找 a.lang的時(shí)候,不僅實(shí)例屬性中沒有,類屬性中也沒有,于是就調(diào)用了__getattr__()方法。在上面的類中,有這個(gè)方法,如果沒有__getattr__()方法呢?如果沒有定義這個(gè)方法,就會(huì)引發(fā) AttributeError,這在前面已經(jīng)看到了。
以上這篇Python對(duì)象的屬性訪問過程詳解就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python一行代碼對(duì)話ChatGPT實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Python一行代碼對(duì)話ChatGPT實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
詳細(xì)介紹在pandas中創(chuàng)建category類型數(shù)據(jù)的幾種方法
這篇文章主要介紹了詳細(xì)介紹在pandas中創(chuàng)建category類型數(shù)據(jù)的幾種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
python目標(biāo)檢測yolo2詳解及預(yù)測代碼復(fù)現(xiàn)
這篇文章主要為大家介紹了python目標(biāo)檢測yolo2詳解及其預(yù)測代碼復(fù)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
基于Python實(shí)現(xiàn)簡單的定時(shí)器詳解
所謂定時(shí)器,是指間隔特定時(shí)間執(zhí)行特定任務(wù)的機(jī)制。幾乎所有的編程語言,都有定時(shí)器的實(shí)現(xiàn)。這篇文章主要介紹的是通過Python實(shí)現(xiàn)的定時(shí)器,感興趣的可以跟隨小編學(xué)習(xí)一下2021-12-12

