Python魔法方法:getattr, getattribute, setattr, delattr

一般狀況下,咱們在訪問類或者實例對象的時候,會牽扯到一些屬性訪問的魔法方法,主要包括:shell

① __getattr__(self, name): 訪問不存在的屬性時調用ide

② __getattribute__(self, name):訪問存在的屬性時調用(先調用該方法,查看是否存在該屬性,若不存在,接着去調用①)spa

③ __setattr__(self, name, value):設置實例對象的一個新的屬性時調用.net

④ __delattr__(self, name):刪除一個實例對象的屬性時調用code

爲了驗證以上,現列出代碼以下:orm

 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的規定無效嗎?對象

不要着急,聽我慢慢道來!blog

 實例對象屬性尋找的順序以下:遞歸

① 首先訪問 __getattribute__() 魔法方法(隱含默認調用,不管何種狀況,均會調用此方法)ci

② 去實例對象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__


那麼方法一和方法二有什麼不一樣呢?仔細看一下你就會發現,其實就是很小的一點不一樣而已:


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 called10 >>> t.x=211 __setattr__() been called
相關文章
相關標籤/搜索