get類型函數
直接上代碼:
class TestMain: def __init__(self): print('TestMain:__init__') self.a = 1 if __name__ == '__main__': t = TestMain() print(t.a)
在沒有任何get函數的情況下很簡單,打印結果是:
TestMain:__init__ 1
但是如果訪問一個不存在的屬性:
if __name__ == '__main__': t = TestMain() print(t.a) print(t.b) # 訪問了一個不存在的屬性
結果是:
TestMain:__init__ Traceback (most recent call last): 1 File "C:/Users/sk-leilin/Desktop/mpdp-code-master/test.py", line 19, in <module> print(t.b) AttributeError: 'TestMain' object has no attribute 'b'
可以看見報錯了,下載我們來測試一下__getattr__
函數:
class TestMain: def __init__(self): print('TestMain:__init__') self.a = 1 def __getattr__(self, item): print('TestMain:__getattr__') return 2 if __name__ == '__main__': t = TestMain() print(t.a) print(t.b)
打印結果是:
TestMain:__init__ 1 TestMain:__getattr__ 2
我們仍然訪問了一個本來不存在的t.b
,為什么這里沒有報錯呢,因為我們定義了__getattr__
函數,而且讓它直接返回了2,也就是說,如果定義了這個函數后,訪問不存在的屬性,會自動調用這個函數作為返回值。
接下來我們看一下__getattribute__
這個函數:
class TestMain: def __init__(self): print('TestMain:__init__') self.a = 1 def __getattr__(self, item): print('TestMain:__getattr__') return 2 def __getattribute__(self, item): print('TestMain:__getattribute__') return 3 if __name__ == '__main__': t = TestMain() print(t.a) print(t.b)
打印結果是:
TestMain:__init__ TestMain:__getattribute__ 3 TestMain:__getattribute__ 3
可以看到,無論是訪問存在的t.a
還是不存在的t.b
,都訪問到了__getattribute__
這個函數,也就是說,只要定義了這個函數,那么屬性的訪問,都會走到這個函數里面。
我們在看下面的代碼:
class TestMain: def __init__(self): print('TestMain:__init__') self.a = 1 def __getattr__(self, item): print('TestMain:__getattr__') return 2 def __getattribute__(self, item): print('TestMain:__getattribute__') if item == 'c': raise AttributeError return 3 if __name__ == '__main__': t = TestMain() print(t.a) print(t.b) print(t.c)
我們知道只要定義了__getattribute__
函數,就肯定執行這個函數來獲取屬性,這次我們增加了判斷如果訪問c
這個屬性,我們拋出異常,最后的結果是:
TestMain:__init__ TestMain:__getattribute__ 3 TestMain:__getattribute__ 3 TestMain:__getattribute__ TestMain:__getattr__ 2
也就是說,如果__getattribute__
拋出了AttributeError
異常,那么會繼續訪問__getattr__
函數的。
總結:
- 如果定義了
__getattribute__
,那么無論訪問什么屬性,都是通過這個函數獲取,包括方法,t.f()
這種也是訪問的這個函數,此時這個函數應該放回一個方法,如果像例子中,仍然返回一個數字,你會獲得一個TypeError: 'int' object is not callable
錯誤- 只要定義了
__getattribute__
方法,不管你訪問一個存在的還是不存在的屬性,都由這個方法返回,比如訪問t.a
,雖然a存在,但是只要定義了這個訪問,那么就不是訪問最開始的a了- 如果
__getattribute__
拋出了AttributeError
異常,並且定了了__getattr__
函數,那么會調用__getattr__
這個函數,不論這個屬性到底是不是存在- 也就是說屬性訪問的一個大致優先級是:
__getattribute__
>__getattr__
>__dict__
單獨說一說__get__
函數
上面說了__getattribute__
和__getattr__
,這里單獨說一下__get__
,因為這個涉及到其它的概念,就是描述器(Descriptor)。
一個類只要實現了
__get__
,__set__
,__delete__
中任意一個方法,我們就可以叫它描述器(descriptor)。如果只定義了__get__
我們叫非資料描述器(non-data descriptor),如果__set__
,__delete__
任意一個/或者同時出現,我們叫資料描述器(data descriptor)。
首先明確一點,擁有這個方法的類,應該(也可以說是必須)產生一個實例,並且這個實例是另外一個類的類屬性(注意一定是類屬性,通過self
的方式產生就不屬於__get__
范疇了)。
也就是說擁有這個方法的類,那么它的實例應該屬於另外一個類/對象的一個屬性。 直接看代碼吧:
class TestDes: def __get__(self, instance, owner): print(instance, owner) return 'TestDes:__get__' class TestMain: des = TestDes() if __name__ == '__main__': t = TestMain() print(t.des) print(TestMain.des)
其中TestDes
定義了__get__
方法,在TestMain
中,定義了一個類屬性des
,是TestDes
的一個實例,我們訪問t.des
或者TestMain.des
的時候訪問的就是訪問了TestDes
的__get__
方法。
打印結果是:
<__main__.TestMain object at 0x0000022563D5D3C8> <class '__main__.TestMain'> TestDes:__get__ None <class '__main__.TestMain'> TestDes:__get__
其中,__get__
方法的第一個參數是實際擁有者的實例,如果沒有則為None
,第二個參數是實際所屬的類。
看一下下面的代碼:
class TestDes: def __get__(self, instance, owner): print(instance, owner) return 'TestDes:__get__' class TestMain: def __init__(self): self.des = TestDes() if __name__ == '__main__': t = TestMain() print(t.des) # print(TestMain.des) #很明顯這里會報錯
我們通過__init__
來產生了一個實例的des
屬性,這時候,print(t.des)
訪問的就不是__get__
函數了,實際打印結果是:
<__main__.TestDes object at 0x00000165A77ECCF8>
也就是當成一個普通的實例來處理的。
非資料描述器,也就是只有
__get__
,不管是類還是實例去訪問,默認都獲得的是__get__
的返回值,但是,如果中間有任何一次重新賦值,那么,這個實例獲得的是新的值(對象),已經和原來的描述器完全脫離了關系
資料描述器,比如有__set__
方法,后期通過實例對描述器進行賦值,那么訪問的是__set__
,並且永遠關聯起來。但是如果通過修改類屬性的方式復制,那么也會被重新獲取新的值(對象)。
看下面的代碼:
class TestDes: def __get__(self, instance, owner): print('TestDes:__get__', instance, owner) return 'TestDes:__get__' class TestMain: des = TestDes() if __name__ == '__main__': t = TestMain() print(t.des) print(TestMain.des) print() t.des = 1 print(t.des) print(TestMain.des) print() TestMain.des = 1 print(t.des) print(TestMain.des)
上面是一個非資料描述器,打印結果是:
TestDes:__get__ <__main__.TestMain object at 0x000002C9BCCF0080> <class '__main__.TestMain'> TestDes:__get__ TestDes:__get__ None <class '__main__.TestMain'> TestDes:__get__ 1 TestDes:__get__ None <class '__main__.TestMain'> TestDes:__get__ 1 1
具體根據上面的描述行為進行分析,就可以得出結果了。
我們在看一下資料描述器:
class TestDes: def __get__(self, instance, owner): print('TestDes:__get__', instance, owner) return 'TestDes:__get__' def __set__(self, instance, value): print('TestDes:__set__', instance, value) # 其它代碼沒有修改
打印結果如下:
TestDes:__get__ <__main__.TestMain object at 0x000002140A46D390> <class '__main__.TestMain'> TestDes:__get__ TestDes:__get__ None <class '__main__.TestMain'> TestDes:__get__ TestDes:__set__ <__main__.TestMain object at 0x000002140A46D390> 1 TestDes:__get__ <__main__.TestMain object at 0x000002140A46D390> <class '__main__.TestMain'> TestDes:__get__ TestDes:__get__ None <class '__main__.TestMain'> TestDes:__get__ 1 1
總結
__getattribute__
和__getattr__
用於實例訪問屬性使用,擁有__get__
方法的類是只能其實例屬於類屬性的時候生效- 只要有
__getattribute__
,任何屬性訪問都是這個的返回值,以下都是在__getattribute__
不存在或者有AttributeError
異常發生的情況下描述的 - 訪問不存在的屬性,
__getattr__
生效 - 訪問存在的屬性,如果是描述器,描述器生效
- 如果通過實例對描述器進行賦值操作,又有資料和非資料描述器的區分,如果定義了
__set__
,那么此方法生效,並且仍然是原始的資料描述器,否則被賦值為新對象 - 描述器賦值如果是通過類的屬性方式賦值,而不是類的實例方式賦值,描述器失效
針對描述器的說明: 描述器是被
__getattribute__
調用的,如果重寫了這個方法,將會阻止自動調用描述器,資料描述器總是覆蓋了實例的__dict__
, 非資料描述器可能覆蓋實例的__dict__
。