天天看點

python屬性查找順序引言作用域對象屬性查找順序繼承屬性查找順序(MRO)

引言

以前對這一部分的了解比較混亂,查找了一些資料,自我整理一下。

作用域

對于變量的搜尋是按照一定順序進行的,同名變量将會存在互相屏蔽的問題,是以需要弄清楚python裡的作用域資訊。在python中作用域一共有四個,按照順序被搜尋:

1. (Local)局部作用域,每當調用一個函數的時候就建立了一個局部作用域,它最先被搜尋。

2. (Enclosing)嵌套的父級函數的局部作用域

3. (global)全局作用域

4. (built-in)内建作用域,這個是内建函數和類的作用域。

變量的搜尋是由内而外一層層進行的,是以内層的變量會屏蔽外層的變量。但是可以通過使用global和nonlocal關鍵字,顯式的聲明一個全局變量或者非局部變量。

對象屬性查找順序

  1. 正常情況下,python中類的屬性儲存在執行個體的

    __dict__

    屬性中,其中

    __dict__

    是一個字典,foo.bar等價于

    foo.__dict__['bar']

    同時對于執行個體的屬性的指派,例如

    foo.bar = 'yes'

    等價于

    foo.__dict__['bar'] = 'yes'

  2. 但是在通路時不會直接進行通路,會調用一個叫做

    __getattribute__

    的函數,對于屬性進行擷取;通過

    __setattribute__

    對于屬性進行修改;但是這些都僅限于

    __dict__

    ,如果找不到對應屬性,則抛出

    AttributeError

  3. 這個時候就會接着調用一個叫做

    __getattr__

    的函數(如果你定義了這個函數),如果這樣都找不到就會真的抛出

    AttributeError

    ,對于

    __setattr__

    也是類似的。
  4. 而我們通常使用的兩個内置函數

    getattr()和setattr()

    實際上也是通過這樣一個調用過程擷取對象的屬性。
  5. 而python 對象還有一個跟

    __getattribute__,__getattr__

    十分類似的一個屬性

    __get__

    ,但是它們的作用卻不太相同,

    所有定義了

    __get__

    屬性的類都會被轉換為一個描述器,具體描述器這裡就不展開了,但是有一點就是,在擷取對象屬性時,

    如果屬性有

    __get__

    方法,那麼就會自動展開

    __get__

    方法。

繼承屬性查找順序(MRO)

MRO的全稱是Method Resolution Order,即方法解析順序,它用來定義類繼承鍊中如何對類屬性和方法進行查找,保證屬性查找不會  
出現沖突。現在實際采用的是C3算法,下面簡單的描述一下:
我們把類C的線性化(MRO)記為L[C]=[C1, C2,…,CN]。其中C稱為L[C]的頭,其餘元素[C2,…,CN]稱為尾。如果一個類C繼承自基類  
B1、B2、……、BN,那麼可以根據以下兩步計算出L[C]:
1. L[object] = [object]
2. L[C(B1…BN)] = [C] + merge(L[B1]…L[BN], [B1]…[BN])
而其中merge函數定義如下:
1. 檢查第一個清單的頭元素(如 L[B1] 的頭),記作 H。
2. 若 H 未出現在其它清單的尾部,則将其輸出,并将其從所有清單中删除,然後回到步驟1;否則,取出下一個清單的頭部  
   記作 H,繼續該步驟。
3. 重複上述步驟,直至清單為空或者不能再找出可以輸出的元素。如果是前一種情況,則算法結束;如果是後一種情況,說  
   明無  法建構繼承關系,Python 會抛出異常。
詳細的例子可以看這裡 [C3算法](https://en.wikipedia.org/wiki/C3_linearization)
在實際使用中,可以通過類的`__mro__`屬性和`mro`方法獲得類的屬性查找順序,傳回結果類似(C, A, B, object),
而與此相關有一個十分常用的函數super,其形式為super(type, [object or type]),實際上super函數不能簡單的看作獲得父 
類對象,實際上它是在mro的基礎清單的基礎上獲得下一個基類對象。第二個參數提供mro清單,第一個參數定位目前位置,  
并傳回清單中的下一個類對象,類似于下面的實作:
           
def super(cls, inst):
    mro = inst.__class__.mro()
    return mro[mro.index(cls)+]