Python中幾種屬性訪問的區別

起步

python的提供一系列和屬性訪問有關的特殊方法:__get__, __getattr__, __getattribute__, __getitem__ 。本文闡述它們的區別和用法。html

屬性的訪問機制

通常狀況下,屬性訪問的默認行爲是從對象的字典中獲取,並當獲取不到時會沿着必定的查找鏈進行查找。例如 a.x 的查找鏈就是,從 a.__dict__['x'] ,而後是 type(a).__dict__['x'] ,再經過 type(a) 的基類開始查找。python

若查找鏈都獲取不到屬性,則拋出 AttributeError 異常。性能

__getattr__ 方法

這個方法是當對象的屬性不存在是調用。若是經過正常的機制能找到對象屬性的話,不會調用 __getattr__ 方法。code

class A:
    a = 1
    def __getattr__(self, item):
        print('__getattr__ call')
        return item

t = A()
print(t.a)
print(t.b)
# output
1
__getattr__ call
b

__getattribute__ 方法

這個方法會被無條件調用。無論屬性存不存在。若是類中還定義了 __getattr__ ,則不會調用 __getattr__() 方法,除非在 __getattribute__ 方法中顯示調用__getattr__() 或者拋出了 AttributeErrorhtm

class A:
    a = 1
    def __getattribute__(self, item):
        print('__getattribute__ call')
        raise AttributeError

    def __getattr__(self, item):
        print('__getattr__ call')
        return item

t = A()
print(t.a)
print(t.b)

因此通常狀況下,爲了保留 __getattr__ 的做用,__getattribute__() 方法中通常返回父類的同名方法:對象

def __getattribute__(self, item):
    return object.__getattribute__(self, item)

使用基類的方法來獲取屬性能避免在方法中出現無限遞歸的狀況。繼承

__get__ 方法

這個方法比較簡單說明,它與前面的關係不大。遞歸

若是一個類中定義了 __get__(), __set__()__delete__() 中的任何方法。則這個類的對象稱爲描述符。文檔

class Descri(object):
    def __get__(self, obj, type=None):
        print("call get")

    def __set__(self, obj, value):
        print("call set")


class A(object):
    x = Descri()

a = A()
a.__dict__['x'] = 1  # 不會調用 __get__
a.x                  # 調用 __get__

若是查找的屬性是在描述符對象中,則這個描述符會覆蓋上文說的屬性訪問機制,體如今查找鏈的不一樣,而這個行文也會由於調用的不一樣而稍有不同:get

  • 若是調用是對象實例(題目中的調用方式),a.x 則轉換爲調用: 。type(a).__dict__['x'].__get__(a, type(a))
  • 若是調用的是類屬性, A.x 則轉換爲:A.__dict__['x'].__get__(None, A)
  • 其餘狀況見文末參考資料的文檔

__getitem__ 方法

這個調用也屬於無條件調用,這點與 __getattribute__ 一致。區別在於 __getitem__ 讓類實例容許 [] 運算,能夠這樣理解:

  • __getattribute__ 適用於全部 . 運算符;
  • __getitem__ 適用於全部 [] 運算符。
class A(object):
    a = 1

    def __getitem__(self, item):
        print('__getitem__ call')
        return item

t = A()
print(t['a'])
print(t['b'])

若是僅僅想要對象可以經過 [] 獲取對象屬性能夠簡單的:

def __getitem(self, item):
    return object.__getattribute__(self, item)

總結

當這幾個方法同時出現可能就會擾亂你了。我在網上看到一份示例還不錯,稍微改了下:

class C(object):
    a = 'abc'

    def __getattribute__(self, *args, **kwargs):
        print("__getattribute__() is called")
        return object.__getattribute__(self, *args, **kwargs)

    #        return "haha"
    def __getattr__(self, name):
        print("__getattr__() is called ")
        return name + " from getattr"

    def __get__(self, instance, owner):
        print("__get__() is called", instance, owner)
        return self

    def __getitem__(self, item):
        print('__getitem__ call')
        return object.__getattribute__(self, item)

    def foo(self, x):
        print(x)


class C2(object):
    d = C()


if __name__ == '__main__':
    c = C()
    c2 = C2()
    print(c.a)
    print(c.zzzzzzzz)
    c2.d
    print(c2.d.a)
    print(c['a'])

能夠結合輸出慢慢理解,這裏還沒涉及繼承關係呢。總之,每一個以 __ get 爲前綴的方法都是獲取對象內部數據的鉤子,名稱不同,用途也存在較大的差別,只有在實踐中理解它們,才能真正掌握它們的用法。

參考

相關文章
相關標籤/搜索