概述
????????了解和熟悉python中的屬性訪問順序,有助于我們閱讀源碼坎藐,編寫高質(zhì)量代碼,對python機制有個更深的理解哼绑。
????????在講解屬性訪問順序之前岩馍,我們先熟悉一下與之有關(guān)的知識,__getattribute__
抖韩,__getattr__
蛀恩,描述符等。隨后我們通過例子來講解python的屬性訪問順序到底是怎樣的帽蝶。
前置知識
- 方法
__getattribute__
- 方法
__getattr__
- 屬性描述符
方法__getattribute__
????????當實例對象訪問屬性或者方法時都需要調(diào)用到__getattribute__
赦肋,之后才會在各個__dict__
中查找相應(yīng)的屬性或方法。注意在方法__getattribute__
內(nèi)部励稳,杜絕存在self.**佃乘,因為這樣的話就又會調(diào)用到__getattribute__
,極可能會遞歸調(diào)用造成錯誤驹尼。
class Test(object):
def __init__(self,name):
self.name = name
def __getattribute__(self,key):
print("訪問屬性:%s" % key)
t = Test("test1")
t.name
輸出結(jié)果:
訪問屬性:name
但是當有實例方法時趣避,調(diào)用實例方法時會報錯,如下
class Test(object):
def __init__(self,name):
self.name = name
def func(self):
pass
def __getattribute__(self,key):
print("訪問屬性:%s" % key)
t = Test("test1")
t.name
t.func()
輸出結(jié)果:
訪問屬性:name
訪問屬性:func
Traceback (most recent call last):
File "c:/Users/DELL/Desktop/ssj/search/descrip.py", line 15, in <module>
t.func()
TypeError: 'NoneType' object is not callable
方法__getattr__
????????在當用戶訪問一個根本不存在的屬性或者查找不到時新翎,會調(diào)用__getattr__
程帕。用于查找屬性的最后一步。
class Test(object):
def __init__(self,name):
self.name = name
def __getattr__(self,key):
print("調(diào)用方法__getattr__: %s" % key)
t = Test("test1")
t.name
t.n
輸出結(jié)果:
調(diào)用方法__getattr__: n
屬性描述符
????????之所以會有描述符的存在地啰,是因為__getattribute__
愁拭、__getattr__
、__setattr__
亏吝、__delattr__
等方法對屬性的一般查找邏輯無法滿足有效的對屬性控制的需求岭埠,假如要實現(xiàn)me.age屬性的類型設(shè)置,單去修改__setattr__
滿足這個需求,那這個方法便有可能對其他的屬性的設(shè)置造成影響惜论。在類中設(shè)置屬性的控制行為不能很好地解決問題许赃,Python給出的方案是:__getattribute__
、__getattr__
馆类、__setattr__
混聊、__delattr__
等方法用來實現(xiàn)屬性查找、設(shè)置乾巧、刪除的一般邏輯句喜,而對屬性的控制行為就由描述符管理。當Test對象訪問x時卧抗,會自動調(diào)用Desc的__get__
方法藤滥。
????????數(shù)據(jù)描述符:不只定義了__get__
方法,還定義了__set__
或者__delete__
社裆。而非數(shù)據(jù)描述符拙绊,只定義了__get__
方法。
以下例子是數(shù)據(jù)描述符示例:
????????實例化Test后泳秀,調(diào)用對象me訪問屬性x标沪,會自動調(diào)用類Desc的__get__
方法,關(guān)于__get__
方法的參數(shù)嗜傅,self代表Desc實例金句,即x,obj代表Test的實例me吕嘀,objtype代表Test這個類违寞。
class Desc(object):
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print ('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print ('Updating', self.name)
self.val = val
class Test(object):
x = Desc(10, 'var "x"')
me = Test(6)
me.x
輸出結(jié)果:
Retrieving var "x"
????????以上例子中,訪問me.x時偶房,會先調(diào)用me的__getattribute__
()方法,再調(diào)用描述符對象的__get__
方法趁曼。
當實例對象的字典中,有與描述符同名的屬性時棕洋。如下例子
class Desc(object):
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print ('Retrieving', self.name)
return 4
def __set__(self, obj, val):
print ('Updating', self.name)
self.val = val
class Test(object):
x = Desc(10, 'var "x"')
def __init__(self,c):
self.x = c
me = Test(6)
me.x
print(me.x)
輸出結(jié)果:
Updating var "x"
Retrieving var "x"
Retrieving var "x"
4
????????以上例子中挡闰,實例化Test時,會先調(diào)用描述符 __set_
()方法掰盘,訪問me.x時摄悯,會調(diào)用描述符 __get_
()方法,最后得出me.x的值是4愧捕,由此可見奢驯,數(shù)據(jù)描述符存在時,會覆蓋掉實例屬性次绘,也就是數(shù)據(jù)描述法的優(yōu)先級高于實例屬性叨橱。
那么非數(shù)據(jù)描述符呢典蜕,與實例屬性的優(yōu)先級比断盛,如何:
class Desc(object):
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print ('Retrieving', self.name)
return 4
class Test(object):
x = Desc(10, 'var "x"')
def __init__(self,c):
self.x = c
self.y = Desc(10, 'var "y"')
me = Test(6)
me.x
print(me.x)
輸出結(jié)果:
6
????????以上例子中罗洗,描述符__set_
()方法和 __get_
()都沒有調(diào)用,說明非數(shù)據(jù)描述符的優(yōu)先級低于實例屬性的钢猛。
屬性訪問順序
__dict__
????????自定義屬性都會有一個字典__dict__
伙菜,包含了所有的實例屬性,不包含實例方法
class Test(object):
x = 1
def __init__(self,c):
self.y = c
def func(self):
pass
@staticmethod
def static():
pass
@classmethod
def cla(cls):
pass
me = Test(6)
print("實例的__dict__屬性",me.__dict__)
print("類的__dict__屬性",Test.__dict__)
輸出結(jié)果:
實例的__dict__屬性 {'y': 6}
類的__dict__屬性 {'static': <staticmethod object at 0x0000014701B280F0>, '__init__': <function Test.__init__ at 0x0000014701B21950>, 'cla': <classmethod object at 0x0000014701B28128>, 'func': <function Test.func at 0x0000014701B219D8>, 'x': 1, '__doc__': None, '__dict__': <attribute '__dict__' of 'Test' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Test' objects>}
????????實例的__dict__
包含了實例屬性y命迈,但是不會有實例方法func和類屬性x贩绕。類的__dict__
包含了其余的東西(類屬性,實例方法壶愤,靜態(tài)方法淑倾,類方法等)。
????????發(fā)生繼承時:
class Test(object):
x = 1
def __init__(self,c):
self.y = c
def func(self):
pass
@staticmethod
def static():
pass
@classmethod
def cla(cls):
pass
class Test1(Test):
z=2
def __init__(self,c):
self.a = c
def func1(self):
pass
@staticmethod
def static1():
pass
@classmethod
def cla1(cls):
pass
me = Test1(6)
print("子類實例的__dict__屬性",me.__dict__)
print("子類的__dict__屬性",Test1.__dict__)
輸出結(jié)果:
子類實例的__dict__屬性 {'a': 6}
子類的__dict__屬性 {'cla1': <classmethod object at 0x000002198524D4A8>, 'static1': <staticmethod object at 0x000002198524D470>, '__module__': '__main__', 'z': 2, '__init__': <function Test1.__init__ at 0x0000021985241BF8>, 'func1': <function Test1.func1 at 0x0000021985241C80>, '__doc__': None}
可見征椒,每個實例對象和類都有自己的__dict__
娇哆,互不影響。
屬性查找順序
其實勃救,正常情況下碍讨,屬性查找都是以一定的規(guī)則從__dict__
中查找的。
????????如果只有類屬性x蒙秒,沒有實例屬性x勃黍,當訪問x的時候,會是如何呢晕讲?我們來看下面例子:
class Test(object):
x = 1
def __init__(self,c):
pass
me = Test(6)
me.x
print(me.x)
輸出結(jié)果:
1
????????以上例子中覆获,表明當沒有實例屬性時即差找不到實例屬性,會查找類屬性瓢省。
注意:當類中定義了__slots__
屬性時弄息,對象就不會有__dict__
屬性了,這時訪問屬性時净捅,是通過類似描述符的方式查找屬性的。
????????一般情況下蛔六,python的屬性訪問機制是:實例屬性荆永,類屬性,父類屬性国章,object屬性具钥;也就是先查找實例對象的__dict__
屬性,沒有的話再查找類__dict__
屬性液兽,再沒有的話查找父類的__dict__
屬性骂删,最后是基類object屬性掌动。當定義了數(shù)據(jù)描述符時時,會覆蓋實例對象的__dict__
屬性宁玫;非數(shù)據(jù)描述符訪問屬性時粗恢,先查找對象的__dict__
屬性,沒有的話再調(diào)用描述符的__get__
方法欧瘪。
總結(jié)一下屬性查找的順序:
-
__getattribute__
()眷射, 無條件調(diào)用 - 數(shù)據(jù)描述符(優(yōu)先于實例屬性)
- 實例對象的字典(與描述符屬性同名時,會被覆蓋哦)
- 非數(shù)據(jù)描述符(只有
__get__
()方法)或者類屬性 -
__getattr__
() 方法(屬性不存在時調(diào)用)