2012-11-24 270 views
17

需要小心編寫方法__getattribute__以避免無限循環。例如:Python:避免__getattribute__中的無限循環

class A: 
    def __init__(self): 
     self.x = 100 

    def __getattribute__(self, x): 
     return self.x 

>>> a = A() 
>>> a.x # infinite looop 
RuntimeError: maximum recursion depth exceeded while calling a Python object 


class B: 
    def __init__(self): 
     self.x = 100 

    def __getattribute__(self, x): 
     return self.__dict__[x] 

>>> b = B() 
>>> b.x # infinite looop 
RuntimeError: maximum recursion depth exceeded while calling a Python object 

因此,我們需要編寫的方法是這樣的:

class C: 
    def __init__(self): 
     self.x = 100 

    def __getattribute__(self, x): 
     # 1. error 
     # AttributeError: type object 'object' has no attribute '__getattr__' 
     # return object.__getattr__(self, x) 

     # 2. works 
     return object.__getattribute__(self, x) 

     # 3. works too 
     # return super().__getattribute__(x) 

我的問題是爲什麼object.__getattribute__方法的工作? object從哪裏得到__getattribute__方法?如果object沒有任何__getattribute__,那麼我們只是在類C上調用相同的方法,但通過超類。爲什麼,然後通過超類調用方法不會導致無限循環?

+2

你確定你需要'__getattribute__'而不是'__getattr__'嗎? –

+4

是的,因爲我需要攔截我班的所有屬性提取。但即使我沒有,我仍然想知道爲什麼這樣的全部細節。 – treecoder

+1

好吧,要麼你必須攔截*所有*屬性訪問或你不:-) –

回答

21

你似乎覺得你的__getattribute__的實現只是一個鉤子,如果你提供它,Python會調用它,否則解釋器會直接執行它的正常魔法。

這是不正確的。當python查找實例的屬性時,__getattribute__是所有屬性訪問的主要條目,而object提供了默認實現。因此你的實現是覆蓋原始的,如果你的實現沒有提供其他返回屬性的方法,它就會失敗。您不能在該方法中使用屬性訪問,因爲通過type(self).__getattribute__(self, attr)再次引導對實例的所有屬性訪問(self)。

解決此問題的最佳方法是再次調用重寫的原件。這就是super(C, self).__getattribute__(attr)進來的地方;你正在問班級解析命令中的下一個班級,以便爲你處理屬性訪問。

或者,您可以直接調用未綁定的object.__getattribute__()方法。此方法的C實現是屬性訪問的最後一站(它可以直接訪問__dict__,因此不會受到相同的限制)。

請注意,super()返回一個代理對象,該代理對象將查找方法解析有序基類中接下來可以找到的任何方法。如果不存在這樣的方法,它將失敗並出現屬性錯誤。它會從來沒有調用原來的方法。因此Foo.bar()查找super(Foo, self).bar將是一個基類實現或屬性錯誤,從來沒有Foo.bar本身。

8

當你這樣做:

return object.__getattribute__(self, x) 

要調用特定的功能 - 一個在對象類中定義的,而不是在一個定義的,所以沒有遞歸。

當你這樣做:

return self.x 

你讓蟒蛇選擇調用哪個函數,並呼籲從A的人,和你有一個無限遞歸。

+0

在'object'類中沒有定義這樣的函數。如果在那裏定義了一個函數,那麼'object'也會定義'__getattr__'(因爲#1錯誤顯示它沒有)。那麼,爲什麼對象只需定義'__getattribute__'方法呢? – treecoder

+1

@greengit:爲什麼不呢?當我訪問'object .__ getattribute__'時,我看到''對象'對象'的槽封裝'__getattribute__'。 –

+0

所以你說'object'只定義'__getattribte__'而不是'__getattr__'?據我所知,所有這些方法的'object'類的實現都是no-op。如果我在某個地方錯了,請糾正我。 – treecoder

2

寫這樣的(ç對象繼承):

class C(object): 
    def __init__(self): 
     self.x = 100 

    def __getattribute__(self, x): 
     return object.__getattribute__(self, x) 

現在你明白爲什麼對象.__的getAttribute __(自我,x)的作品 - 您呼叫的父對象。