通常情況下,我們在通路類或者執行個體對象的時候,會牽扯到一些屬性通路的魔法方法,主要包括:
① __getattr__(self, name): 通路不存在的屬性時調用
② __getattribute__(self, name):通路存在的屬性時調用(先調用該方法,檢視是否存在該屬性,若不存在,接着去調用①)
③ __setattr__(self, name, value):設定執行個體對象的一個新的屬性時調用
④ __delattr__(self, name):删除一個執行個體對象的屬性時調用
為了驗證以上,現列出代碼如下:
1 class Test:
2 def __getattr__(self, name):
3 print('__getattr__')
4
5 def __getattribute__(self, name):
6 print('__getattribute__')
7
8 def __setattr__(self, name, value):
9 print('__setattr__')
10
11 def __delattr__(self, name):
12 print('__delattr__')
13
14 >>> t=Test()
15 >>> t.x
16 __getattribute__
如上述代碼所示,x并不是Test類執行個體t的一個屬性,首先去調用 __getattribute__() 方法,得知該屬性并不屬于該執行個體對象;但是,按照常理,t.x應該列印 __getattribute__ 和__getattr__,但實際情況并非如此,為什麼呢?難道以上Python的規定無效嗎?
不要着急,聽我慢慢道來!
執行個體對象屬性尋找的順序如下:
① 首先通路 __getattribute__() 魔法方法(隐含預設調用,無論何種情況,均會調用此方法)
② 去執行個體對象t中查找是否具備該屬性: t.__dict__ 中查找,每個類和執行個體對象都有一個 __dict__ 的屬性
③ 若在 t.__dict__ 中找不到對應的屬性, 則去該執行個體的類中尋找,即 t.__class__.__dict__
④ 若在執行個體的類中也招不到該屬性,則去父類中尋找,即 t.__class__.__bases__.__dict__中尋找
⑤ 若以上均無法找到,則會調用 __getattr__ 方法,執行内部的指令(若未重載 __getattr__ 方法,則直接報錯:AttributeError)
以上幾個流程,即完成了屬性的尋找。
但是,以上的說法,并不能解釋為什麼執行 t.x 時,不列印 ’__getattr__' 啊?
你看,又急了不是,作為一名程式猿,一定要有耐心 ^_^
問題就出在了步驟的第④步,因為,一旦重載了 __getattribute__() 方法,如果找不到屬性,則必須要手動加入第④步,否則無法進入到 第⑤步 (__getattr__)的。
驗證一下以上說法是否正确:
方法一:采用object(所有類的基類)
1 class Test:
2 def __getattr__(self, name):
3 print('__getattr__')
4
5 def __getattribute__(self, name):
6 print('__getattribute__')
7 object.__getattribute__(self, name)
8
9 def __setattr__(self, name, value):
10 print('__setattr__')
11
12 def __delattr__(self, name):
13 print('__delattr__')
14
15
16 >>> t=Test()
17 >>> t.x
18 __getattribute__
19 __getattr__
怎麼樣,顯示出來了吧?哈哈
方法二:采用 super() 方法
1 class Test:
2 def __getattr__(self, name):
3 print('__getattr__')
4
5 def __getattribute__(self, name):
6 print('__getattribute__')
7 super().__getattribute__(name)
8
9 def __setattr__(self, name, value):
10 print('__setattr__')
11
12 def __delattr__(self, name):
13 print('__delattr__')
14
15
16 >>> t=Test()
17 >>> t.x
18 __getattribute__
19 __getattr__
哈哈,醬紫也可以哦 ^v^
那麼方法一和方法二有什麼不同呢?仔細看一下你就會發現,其實就是很小的一點不同而已:
1 #方法一:使用基類object的方法
2 def __getattribute__(self, name):
3 print('__getattribute__')
4 object.__getattribute__(self, name)
5
6 #方法二:使用super()方法(有的認為super()是類,此處暫以方法處理)
7 def __getattribute__(self, name):
8 print('__getattribute__')
9 super().__getattribute__(name)
在Python2.x中,以上super的用法應該改為 super(Test, self).xxx,但3.x中,可以像上面代碼一樣簡單使用。
那麼super到底是什麼東西呢?如果想詳細的了解,請點選這裡
哈哈,以上介紹完畢,那麼 __setattr__ 和 __delattr__ 方法相對簡單了多了:
1 class Test:
2 def __getattr__(self, name):
3 print('__getattr__')
4
5 def __getattribute__(self, name):
6 print('__getattribute__')
7 object.__getattribute__(self, name)
8
9 def __setattr__(self, name, value):
10 print('__setattr__')
11
12 def __delattr__(self, name):
13 print('__delattr__')
14
15
16 >>> t=Test()
17 >>> t.x=10
18 __setattr__
19 >>> del t.x
20 __delattr__
至此,關于屬性通路,先告一段落,後續會有更進階的descirptor。
對了,再補充一點哈!
1 class Test:
2 def __init__(self):
3 self.count = 0
4 def __setattr__(self, name, value):
5 print('__setattr__')
6 self.count += 1
7
8
9 >>> t=Test()
10 __setattr__
11 Traceback (most recent call last):
12 File "<pyshell#364>", line 1, in <module>
13 t=Test()
14 File "<pyshell#363>", line 3, in __init__
15 self.count = 0
16 File "<pyshell#363>", line 6, in __setattr__
17 self.count += 1
18 AttributeError: 'Test' object has no attribute 'count'
為什麼會出現上述情況呢?我還沒有調用__setattr__()呢,隻是單純的定義了一個執行個體而已? @_@(咋回事?幻覺嗎)
看報錯資訊很容易明白,這是因為:
① __init__()時,給内部屬性 self.count進行了指派;
② 指派預設調用 __setattr__() 方法
③ 當調用 __setattr__()方法時,首先列印 '__setattr__'字元串,而後執行 self.cout += 1操作
④ 當執行 self.cout 加 1 操作時,将會去尋找 count 這個屬性,然而,由于此時 __init__尚未完成,并不存在 count這個屬性,是以導緻 'AttributeError' 錯誤
那麼該如何更改呢?可以這樣的:
1 class Test:
2 def __init__(self):
3 self.count = 0
4 def __setattr__(self, name, value):
5 print('__setattr__')
6 super().__setattr__(name, value+1)
7
8
9 >>> t=Test()
10 __setattr__
11 >>> t.count
12 1
如何,問題解決了吧!
但是以上代碼雖然解決了報錯的問題,深入體會一下,你會發現,采用此方法隻是給 基類object增加了一個屬性 count,而并不是執行個體的屬性,是以,以上這種寫法避免使用
另外,再次将代碼改進一下,如下:
1 class Test:
2 def __setattr__(self, name, value):
3 self.name = value
4
5
6 >>> t=Test()
7 >>> t.x=1
8 Traceback (most recent call last):
9 File "<pyshell#413>", line 1, in <module>
10 t.x=1
11 File "<pyshell#411>", line 3, in __setattr__
12 self.name = value
13 File "<pyshell#411>", line 3, in __setattr__
14 self.name = value
15 File "<pyshell#411>", line 3, in __setattr__
16 self.name = value
17 [Previous line repeated 327 more times]
18 RecursionError: maximum recursion depth exceeded while calling a Python object
居然報錯了,看報錯資訊為 “遞歸錯誤”,我沒用遞歸啊,怎麼會有這個錯誤呢?
其實,原因很簡單:當我們給 t.x 指派時,調用了 __setattr__()方法,進入該方法;該方法中,又來了一次指派(self.name = value),又會去調用 __setattr__() 方法,持續這個死循環(子子孫孫無窮盡也,必須要有一代斷子絕孫);
我們知道,系統的資源是有限的,丫的你老是申請資源不釋放,系統哪來的那麼多資源給你自己用?是以,Python解釋器規定,遞歸深度不得超過200(不同版本不一樣),你超過了,不好意思,不帶你玩了!
是以,我們隻好改變上述的問題了:
1 t.x=2
2 >>> class Test:
3 def __setattr__(self, name, value):
4 print('__setattr__() been called')
5 super().__setattr__(name, value)
6
7 >>> t=Test()
8 >>> t.x=1
9 __setattr__() been called
10 >>> t.x=2
11 __setattr__() been called
OK,至此,關于屬性操作的問題暫時完結吧!