python2.x中的新類型類(New-style class)與python3.x的類一致,均繼承object類,而不繼承object的類稱爲經典類(classic class),而對於這兩種類,通常實例屬性截取函數(generic instance attribute interception methods)的行爲有所不一樣,其在3.x和2.x的新類型類中,再也不被__x__操做符重載函數名(operator overloading name)的內建操做調用,對於該操做符重載函數名的搜索直接在類中搜索,而非實例中,而對於顯式名的屬性獲取,包括__x__名,仍然要路經__getattr__,所以這是對於內建操做行爲的主要影響,這種影響進而又影響到屬性截取以及代理類。好比一個類定義了__getitem__索引重載函數,x是該類的一個實例,對於經典類來講,x[I]與x.__getitem__(I)等價,而對於新類型類來講,x[I]再也不被__getattr__獲取,而顯式x.__getitem__仍然能夠被獲取。python
1.對屬性截取的影響:shell
首先看__getattr__在經典類與新類型類中表現的差別。python3.x
(1)在新類型類中(下列代碼在3.x中實現):函數
構造了一個名爲c的類,類長__getattr__方法可截取實例屬性,而後打印截取到的屬性名,最後返回實例對象的data對象的name方法結果。而對於x[0]內建操做表達式,則拋出了異常,該異常爲c對象不支持索引,所以能夠看出x[0]是直接在類中進行搜索,而跳過了實例屬性截取函數__getattr__。測試
>>> getattr->__getitem__ x.__getitem__(0) getattr->__getitem__ 's'
而x.__getitem__(0)方法能夠被__getattr__獲取,相似的,對於其餘內建操做,好比,x+'eggs',與x.__add__('eggs'),也有相同的反應。ui
>>> getattr->__add__ x.__add__('eggs') getattr->__add__ 'spameggs' >>> x+'eggs' Traceback (most recent call last): File "<pyshell#12>", line 1, in <module> x+'eggs' TypeError: unsupported operand type(s) for +: 'c' and 'str'
>>> type(x).__getitem__(x,0)
Traceback (most recent call last):
File "<pyshell#18>", line 1, in <module>
type(x).__getitem__(x,0)
AttributeError: type object 'c' has no attribute '__getitem__'
當用x的類(即c)調用__getitem__,能夠預想到的,拋出AttributeError,由於c並無__getitem__方法。spa
(2)以上代碼在經典類中(在2.x中實現):代理
>>> class c: data='spam' def __getattr__(self,name): print('getattr->'+name) return getattr(self.data,name) File "<pyshell#0>", line 2 class c: ^ IndentationError: unexpected indent >>> class c: data='spam' def __getattr__(self,name): print('getattr->'+name) return getattr(self.data,name) >>> x=c() >>> x[0] getattr->__getitem__ 's' >>> getattr->__getitem__ x.__getitem__(0) getattr->__getitem__ 's' >>> getattr->__add__ x.__add__('eggs') getattr->__add__ 'spameggs' >>> x+'eggs' getattr->__coerce__ getattr->__add__ 'spameggs'
能夠看到,在經典類型中,測試所有經過。code
>>> type(x).__getitem__(0) Traceback (most recent call last): File "<pyshell#8>", line 1, in <module> type(x).__getitem__(0) TypeError: descriptor '__getitem__' requires a 'instance' object but received a 'int'
可是,嘗試用c類調用__getitem__,卻拋出異常,主要是描述符(descriptor)的參數錯誤形成的,關於描述符的總結,將在後面的文章中專門整理。對象
2.對代理類的影響
實際上,在屬性截取中,已經提到,在新類型類中,當直接用隱式的內建操做表達式,如x[i],x+等,拋出AttributError的異常,由於這種狀況下,是直接從類開始搜索的,而c類中沒有,因此才拋出了異常,那該怎麼辦呢?一個很天然的辦法就是在類中,對要代理的隱式內建操做表達式進行從新定義,因此類就具有了要代理操做屬性。
>>> class c: data='spam' def __getattr__(self,name): print('getattr->'+name) return getattr(self.data,name) def __getitem__(self,i): print('getitem:'+str(i)) return self.data[i] def __add__(self,other): print('add->'+other) return getattr(self.data,'__add__')(other)
上述代碼在3.x中實現,經過對類c從新定義__getitem__,__add__從新定義實現了代理索引和加操做。
>>> x=c() >>> x.upper() getattr->upper 'SPAM'
能夠看到__getattr__截取了通常方法upper()。
>>> x[0] getitem:0 's' >>> x.__getitem__(0) getitem:0 's' >>> x+'eggs' add->eggs 'spameggs' >>> x.__add__('eggs') add->eggs 'spameggs'
能夠看到,代理成功。
(3)進一步的理解
事實上,子類繼承基類(超類)的屬性或者方法若在子類中沒有重載,而子類實例若調用該屬性,將不被__getattr__攔截,直接調用基類的屬性。以下代碼:
>>> class c: def test(self): print('test from c') >>> class d(c): def __getattr__(self,attr): print('getattr'+attr) >>> x=d() >>> x.test() test from c