Attribute Lookup: 當訪問實例的屬性時棋枕,發(fā)生了什么

原文地址, 拒絕轉載: http://www.reibang.com/p/b2691cf186d4

Attribute Lookup

假設 Cls 是類,instance 是類 Cls 的一個實例省有,當調用 instance.attr 時硬梁,到底發(fā)生了什么呢?下面就來一一探討屬性訪問的調用流程

1 descriptor

什么是 desciptor, 官方文檔給出的回答是

A descriptor is what we call any object that defines __get__(), __set__(), or __delete__().

即包含了任意 __get__ 或者 __set__ 或者 __delete__ 函數的方法的 object 都是 descriptor

2 data descriptor 與 non-data descriptors

If an object defines __set__() or __delete__(), it is considered a data descriptor. Descriptors that only define __get__() are called non-data descriptors (they are typically used for methods but other uses are possible).

如果一個 descriptor 只定義了 __get__ 方法淮野,那么就是 non-data descriptor

如果一個 object 定義了 __set__ 或者 __delete__ 方法,那么就是 data descriptor

class DataDescriptor:
    """
    包含了 __set__ 方法吹泡,所以這個類的實例是 data-descritptor
    """
    def __init__(self, init_value):
        self.value = init_value

    def __get__(self, instance, typ):
        return 'DataDescriptor __get__' + str(typ)

    def __set__(self, instance, value):
        print ('DataDescriptor __set__')
        self.value = value

class NonDataDescriptor:
    """
    只定義了 __get__ 方法骤星,所以這個類的實例是 non-data descriptor
    """
    def __init__(self, init_value):
        self.value = init_value

    def __get__(self, instance, typ):
        return'NonDataDescriptor __get__' + str(typ)
3 當調用 instance.attr 時,發(fā)生了什么

假設 cls 是類爆哑,instance 是類 cls 的一個實例洞难,當調用 instance.attr 時,調用流程如下

  • 如果在cls 或者 其基類中的 __dict__ 找到了 attr揭朝,并且 attrdata descriptor 則調用其 __get__方法队贱,即 __dict__['attr'].__get(instance, cls)

    class Base(object):
        dd_base = DataDescriptor(0)
        ndd_base = NonDataDescriptor(0)
    
    class Derive(Base):
        dd_derive = DataDescriptor(0)
        ndd_derive = NonDataDescriptor(0)
        ndd_derive2 = NonDataDescriptor(1)
        not_descriptor_in_class = "Derive not descriptor in class"
        
        def __getattr__(self, key):
            return '__getattr__ with key %s in Derive' % key
    
    print(Base.__dict__)
    """
    {
    '__module__': '__main__', 
    'dd_base': <__main__.DataDescriptor object at 0x7fc5c5b68a58>, 
    'ndd_base': <__main__.NonDataDescriptor object at 0x7fc5c5b68a90>, 
    '__dict__': <attribute '__dict__' of 'Base' objects>, 
    '__weakref__': <attribute '__weakref__' of 'Base' objects>, 
    '__doc__': None}
    """
    print(Derive.__dict__)
    """
    {'__module__': '__main__', 
    'dd_derive': <__main__.DataDescriptor object at 0x7f9e74ac79b0>, 
    'ndd_derive': <__main__.NonDataDescriptor object at 0x7f9e74ac79e8>, 'same_name_attr': 'attr in class', 
    '__doc__': None}
    """
    b = Base()
    # 打印: DataDescriptor __get__<class '__main__.Base'>
    print(b.dd_base)
    d = Derive()
    # 打印: DataDescriptor __get__<class '__main__.Derive'>
    print(d.dd_base)
    # 打印: DataDescriptor __get__<class '__main__.Derive'>
    print(d.dd_derive)
    
    # 即使我們更改了 instance 的 __dict__ 屬性,訪問時仍然從 data descriptor 中讀取
    # 不會從 instance.__dict__ 中讀取
    b.__dict__['dd_base'] = 'changed in dict dd base'
    # 打印: DataDescriptor __get__<class '__main__.Base'>
    print(b.dd_base)
    d.__dict__['dd_derive'] = 'changed in dict dd derive'
    # 打印: DataDescriptor __get__<class '__main__.Derive'>
    print(d.dd_derive)
    
    
  • 如果 attr 出現在 instance.__dict__ 中萝勤,則返回 instance.__dict__['attr']露筒。否則,執(zhí)行下面的流程

    # 更改了 instance 的 __dict__
    # 如果訪問的不是 data descriptor, 則直接中 instance.__dict__ 中讀取 attr
    b.__dict__['ndd_base'] = 'changed in dict ndd base'
    # 打印: changed in dict ndd base
    print(b.ndd_base)
    d.__dict__['ndd_derive'] = 'changed in dict ndd derive'
    # 打印: changed in dict ndd derive
    print(d.ndd_derive)
    
  • 如果 attr 出現在類或者基類的 __dict__

    • 如果是 non-data descriptor, 則調用 __get__方法

      # 打印: NonDataDescriptor __get__<class '__main__.Derive'>
      print(d.ndd_derive2)
      
    • 如果不是 descriptor , 則返回 __dict__['attr']

      # 打印: Derive not descriptor in class
      print(d.not_descriptor_in_class)
      
  • 如果仍未找到敌卓,如果類或者其基類有 __getattr__ 方法慎式,則調用 __getattr__ 方法

    # 打印: __getattr__ with key no_exist_key in Derive
    print(d.no_exist_key)
    
  • 否則拋出 AttributeError

    try:
        b.no_exists_key
    except Exception as e:
        # 打印: True
        print(isinstance(e, AttributeError))
    
4 完整測試代碼
class DataDescriptor:
    """
    包含了 __set__ 方法,所以這個類的實例是 data-descritptor
    """

    def __init__(self, init_value):
        self.value = init_value

    def __get__(self, instance, typ):
        return 'DataDescriptor __get__' + str(typ)

    def __set__(self, instance, value):
        print('DataDescriptor __set__')
        self.value = value


class NonDataDescriptor:
    """
    只定義了 __get__ 方法趟径,所以這個類的實例是 non-data descriptor
    """

    def __init__(self, init_value):
        self.value = init_value

    def __get__(self, instance, typ):
        return 'NonDataDescriptor __get__' + str(typ)


class Base(object):
    dd_base = DataDescriptor(0)
    ndd_base = NonDataDescriptor(0)


class Derive(Base):
    dd_derive = DataDescriptor(0)
    ndd_derive = NonDataDescriptor(0)
    ndd_derive2 = NonDataDescriptor(1)
    not_descriptor_in_class = "Derive not descriptor in class"

    def __getattr__(self, key):
        return '__getattr__ with key %s in Derive' % key


if __name__ == '__main__':
    b = Base()
    # 打印: DataDescriptor __get__<class '__main__.Base'>
    print(b.dd_base)
    d = Derive()
    # 打印: DataDescriptor __get__<class '__main__.Derive'>
    print(d.dd_base)
    # 打印: DataDescriptor __get__<class '__main__.Derive'>
    print(d.dd_derive)

    # 即使我們更改了 instance 的 __dict__ 屬性瘪吏,訪問時仍然從 data descriptor 中讀取
    # 不會從 instance.__dict__ 中讀取
    b.__dict__['dd_base'] = 'changed in dict dd base'
    # 打印: DataDescriptor __get__<class '__main__.Base'>
    print(b.dd_base)
    d.__dict__['dd_derive'] = 'changed in dict dd derive'
    # 打印: DataDescriptor __get__<class '__main__.Derive'>
    print(d.dd_derive)

    # 更改了 instance 的 __dict__
    # 如果訪問的不是 data descriptor, 則直接中 instance.__dict__ 中讀取 attr
    b.__dict__['ndd_base'] = 'changed in dict ndd base'
    # 打印: changed in dict ndd base
    print(b.ndd_base)
    d.__dict__['ndd_derive'] = 'changed in dict ndd derive'
    # 打印: changed in dict ndd derive
    print(d.ndd_derive)

    # 打印: NonDataDescriptor __get__<class '__main__.Derive'>
    print(d.ndd_derive2)
    # 打印: Derive not descriptor in class
    print(d.not_descriptor_in_class)

    # 打印: __getattr__ with key no_exist_key
    print(d.no_exist_key)

    try:
        b.no_exists_key
    except Exception as e:
        # 打印: True
        print(isinstance(e, AttributeError))
5 參考鏈接

https://blog.peterlamut.com/2018/11/04/python-attribute-lookup-explained-in-detail/
https://docs.python.org/3/howto/descriptor.html
https://www.cnblogs.com/xybaby/p/6270551.html

6 轉載一下調用流程圖片

圖片是從參考鏈接的第一個博客中復制的,把調用的流程描述的很清晰蜗巧,值得一看


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末掌眠,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子幕屹,更是在濱河造成了極大的恐慌蓝丙,老刑警劉巖级遭,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異渺尘,居然都是意外死亡挫鸽,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門鸥跟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丢郊,“玉大人,你說我怎么就攤上這事医咨》阖遥” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵拟淮,是天一觀的道長干茉。 經常有香客問我,道長惩歉,這世上最難降的妖魔是什么等脂? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮撑蚌,結果婚禮上,老公的妹妹穿的比我還像新娘搏屑。我一直安慰自己争涌,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布辣恋。 她就那樣靜靜地躺著亮垫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伟骨。 梳的紋絲不亂的頭發(fā)上饮潦,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音携狭,去河邊找鬼继蜡。 笑死,一個胖子當著我的面吹牛逛腿,可吹牛的內容都是我干的稀并。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼单默,長吁一口氣:“原來是場噩夢啊……” “哼碘举!你這毒婦竟也來了?” 一聲冷哼從身側響起搁廓,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤引颈,失蹤者是張志新(化名)和其女友劉穎耕皮,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體蝙场,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡明场,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了李丰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苦锨。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趴泌,靈堂內的尸體忽然破棺而出舟舒,到底是詐尸還是另有隱情,我是刑警寧澤嗜憔,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布秃励,位于F島的核電站,受9級特大地震影響吉捶,放射性物質發(fā)生泄漏夺鲜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一呐舔、第九天 我趴在偏房一處隱蔽的房頂上張望币励。 院中可真熱鬧,春花似錦珊拼、人聲如沸食呻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仅胞。三九已至,卻和暖如春剑辫,著一層夾襖步出監(jiān)牢的瞬間干旧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工妹蔽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留椎眯,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓讹开,卻偏偏與公主長得像盅视,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子旦万,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359