2017-08-30 17 views
1

我想構建一個系統,其中每個其他對象都使用一個基類。每個基礎對象在內部都有一個_fields字典,其中基類的實現可以存儲其信息。結合__setattr__和__getattr__導致無限循環

基類的實現很簡單:

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

類的實現可以設置在__init__調用其super()領域。

我想補充說的是,這些字段可以作爲屬性訪問,而不必將@property修飾器添加到一大堆函數中。我已經重寫了__getattr__用於此目的的基類,像這樣:

class A_impl(A): 
    def __init__(self): 
     super(A_impl, self).__init__(
        fields=dict(
        val_1="foo", 
        val_2="", 
        val_3="bar", 
       ) 
      ) 

通過它創建該類的實現提供:

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

    def __getattr__(self, name): 
     if hasattr(self, name): 
      return object.__getattribute__(self, name) 
     elif name in self._fields: 
      return self._fields.get(name) 
     else: 
      raise AttributeError 

現在對於這個類實現可以這樣工作你選擇做:

test = A_imp() 
print test.val_1 
print test.val_2 
print test.val_3 

它返回

foo 

bar 

我甚至可以通過@property裝飾覆蓋此,改變類,像這樣:

class A_impl(A): 
    def __init__(self): 
     super(A_impl, self).__init__(
        fields=dict(
        val_1="foo", 
        val_2="", 
        val_3="bar", 
       ) 
      ) 

    @property 
    def val_1(self): 
     return self._fields.get('val_1') + "_getter" 

,讓我來操縱數據的回報。唯一的問題是,如果我想能夠設置這些字段變量之一,我必須實現描述符設置函數,這也需要我做屬性描述符,它創造了很多重複的工作(即我必須定義我的所有字段描述這是我想避免在這裏。

我實現了__setattr__功能的基類來解決地方如果我手動實現它應該在默認這是self._field[name] = value選擇一個描述符setter函數問題基類現在看起來像這樣(類似於__getattr__):

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

    def __getattr__(self, name): 
     if hasattr(self, name): 
      return object.__getattribute__(self, name) 
     elif name in self._fields: 
      return self._fields.get(name) 
     else: 
      raise AttributeError 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      object.__setattr__(self, name, value) 
     elif name in self._fields: 
      self._fields[name] = value 
     else: 
      raise AttributeError 

現在,如果我再次運行相同的代碼測試:

test = A_imp() 
print test.val_1 
print test.val_2 
print test.val_3 

,瞬間陷在一個無限循環,它開始在__setattr__但跳進右後__getattr__,並保持該循環。

我一直在閱讀很多關於這個stackoverflow的問題,無法弄清楚,這就是爲什麼我建立這個測試用例,以清楚地弄清楚它。希望有人能夠爲我澄清這一點,並幫助我解決問題。

+0

'hasattr'是不必要的。 – o11c

回答

3

無法檢查某個對象是否具有實際嘗試檢索它的屬性。因此,

hasattr(self, 'val_1') 

實際上試圖訪問self.val_1。如果self.val_1沒有通過正常方式找到,它會回退__getattr__,它再次以無限遞歸方式調用hasattr

hasattr實際上捕捉到任何異常,包括RuntimeError: maximum recursion depth exceeded,並且返回False,因此這種體現的確切方式取決於調用嵌套的調用次數。某些__getattr__調用遇到了object.__getattribute__的情況並引發了AttributeError;一些打到elif name in self._fields: return self._fields.get(name)的情況。如果self._fields存在,則返回一個值,但與您的__setattr__,有時self._fields不存在!


當你__setattr__試圖處理在__init__self._fields分配,它調用hasattr(self, '_fields'),這就要求__getattr__。現在一些__getattr__調用進行了兩次遞歸__getattr__調用,一個調用hasattr,另一個調用elif name in self._fields。由於hasattr正在捕捉異常,因此這會導致遞歸調用在遞歸深度中呈指數形式,而不是快速地看似工作或引發異常。


不要在__getattr____getattribute__使用hasattr。一般來說,要非常小心所有嘗試訪問處理屬性訪問的方法內的屬性。

+0

那麼我會怎麼做呢?我希望優先於自我實現的描述符,而不是始終提供self._field條目。 – Yonathan

+0

@Yonathan:只有在「正常」查找(通過__getattribute__')找不到值時纔會調用__getattr__',所以您實際上不必再次檢查。至於如何對通常的屬性訪問「非常小心」,通常需要委派給'super().__ getattribute__'或'super().__ setattr__'來進行任何需要繞過特殊邏輯的屬性檢索或分配。 – user2357112

+0

但是在set中使用類似'if name in self._fields'的方法,並將其設置爲'super().__(set | set)attr __(self,name [,value])'並獲取基類的attr函數導致相同的遞歸問題。我看不出我該如何解決「標準行爲第一,否則檢查self._fields」問題。現在優先考慮的是'__(get | set)attr__'函數,而不是實現的描述符。對不起,我只是想我怎麼能解決這個問題。 – Yonathan