python 屬性訪問順序 描述符

概述

????????了解和熟悉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é)一下屬性查找的順序:
  1. __getattribute__()眷射, 無條件調(diào)用
  2. 數(shù)據(jù)描述符(優(yōu)先于實例屬性)
  3. 實例對象的字典(與描述符屬性同名時,會被覆蓋哦)
  4. 非數(shù)據(jù)描述符(只有__get__()方法)或者類屬性
  5. __getattr__() 方法(屬性不存在時調(diào)用)

如有不當之處佛掖,歡迎交流指正妖碉!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芥被,隨后出現(xiàn)的幾起案子欧宜,更是在濱河造成了極大的恐慌,老刑警劉巖拴魄,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冗茸,死亡現(xiàn)場離奇詭異,居然都是意外死亡羹铅,警方通過查閱死者的電腦和手機蚀狰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來职员,“玉大人麻蹋,你說我怎么就攤上這事『盖校” “怎么了扮授?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長专肪。 經(jīng)常有香客問我刹勃,道長,這世上最難降的妖魔是什么嚎尤? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任荔仁,我火速辦了婚禮,結(jié)果婚禮上芽死,老公的妹妹穿的比我還像新娘乏梁。我一直安慰自己,他們只是感情好关贵,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布遇骑。 她就那樣靜靜地躺著,像睡著了一般揖曾。 火紅的嫁衣襯著肌膚如雪落萎。 梳的紋絲不亂的頭發(fā)上亥啦,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音练链,去河邊找鬼翔脱。 笑死,一個胖子當著我的面吹牛兑宇,可吹牛的內(nèi)容都是我干的碍侦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼隶糕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了站玄?” 一聲冷哼從身側(cè)響起枚驻,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎株旷,沒想到半個月后再登,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡晾剖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年锉矢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片齿尽。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡沽损,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出循头,到底是詐尸還是另有隱情绵估,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布卡骂,位于F島的核電站国裳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏全跨。R本人自食惡果不足惜缝左,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浓若。 院中可真熱鬧渺杉,春花似錦、人聲如沸七嫌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诵原。三九已至英妓,卻和暖如春挽放,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔓纠。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工辑畦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腿倚。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓纯出,卻偏偏與公主長得像,于是被迫代替她去往敵國和親敷燎。 傳聞我的和親對象是個殘疾皇子暂筝,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359