__getattr__在python2.x與python3.x中的區別及其對屬性截取與代理類的影響

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中實現):函數

>>> class c:
 data='spam'
 def __getattr__(self,name):
  print('getattr->'+name)
  return getattr(self.data,name)
 
>>> x=c()
>>> x[0]
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    x[0]
TypeError: 'c' object does not support indexing

構造了一個名爲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
相關文章
相關標籤/搜索