2014-12-03 73 views
2

我一整天都在與這個戰鬥,並且做了大量的Google搜索。我有什麼似乎是繼承問題。Python繼承和__getattr__&__getattribute__

我有一個名爲BaseClass的類,它爲我執行一些簡單的事情,比如設置日誌記錄默認值,保存日誌和管理只讀屬性。在過去,我對這門課沒有任何問題,但我可以說我現在只用了幾個月的python。此外,還有一些我認爲很重要的注意事項:

  1. 所有過去的繼承都是單一的繼承。換句話說,我繼承了BaseClass,但繼承BaseClass的類不會被另一個類繼承。在今天的問題中,我將BaseClass繼承到BaseFormData中,然後將它繼承到July2013FormData,然後繼承到Jan2014FormData。所以顯然現在有更多的圖層。
  2. 我一直沒有試圖在過去繼承BaseClass的任何類中重寫__getattr____getattribute__。今天我是。 BaseClass有自己的__getattribute__方法來管理獲取只讀屬性。 BaseFormData類有一個__getattr__方法,因爲我讀過這是提供數據訪問的正確方法,而不必顯式聲明所有屬性。被加載的表單數據中有十幾個或更多的數據(取決於版本),所以我明確聲明瞭一些重要或別名的屬性,其餘的則由__getattr__來處理。

這裏是BaseClass的將__getattribute__

def __getattribute__(self, attr): 
     try: 
      # Default behaviour 
      return object.__getattribute__(self, attr) 
     except: 
      try: 
       return self.__READ_ONLY[attr] 
      except KeyError: 
       lcAttr = php_basic_str.strtolower(attr) 
       return self._raw[lcAttr] 

使用self._raw的線是有完全是因爲繼承BaseClass的類時常使用_raw。 BaseClass不需要_raw。理想情況下,參考_raw只會出現在繼承類的__getattr____getattribute__中。但是,我需要在BaseClass中使用__getattribute__才能使「只讀」功能正常工作。

而且__getattr__從BaseFormData:

def __getattr__(self, name): 
     """ 
     Return a particular piece of data from the _raw dict 
     """ 

     # All the columns are saved in lowercase 
     lcName = s.strtolower(name) 

     try: 
      tmp = self._raw[lcName] 
     except KeyError: 
      try: 
       tmp = super(BaseFormData, self).__getattribute__(name) 
      except KeyError: 
       msg = "'{0}' object has no attribute '{1}'. Note that attribute search is case insensitive." 
       raise AttributeError(msg.format(type(self).__name__, name)) 

     if tmp == 'true': 
      return True 
     elif tmp == 'false': 
      return False 
     else: 
      return tmp 

這是我收到的錯誤:

File "D:\python\lib\mybasics\mybasics\cBaseClass.py", line 195, in __getattribute__ 
    return self.__READ_ONLY[attr] 

RuntimeError: maximum recursion depth exceeded while calling a Python object 

簡短的版本是,我已經打了一整天這一點,因爲這裏有一個調整或者我得到不同的迴應。從BaseFormData大多數時間__getattr__永遠不會被調用。其他時候我得到這個遞歸錯誤。其他時間我可以開始工作,但我添加了一些小東西,一切都再次打破。

有些東西我明顯遺漏了繼承問題以及調用__getattribute____getattr__的順序。我今天做了很多代碼調整,如果內存服務,我不能在BaseFormData和BaseClass中使用__getattribute__,但我不記得我的頭頂上的錯誤。

我猜遞歸問題源於該行__getattr__

tmp = super(BaseFormData, self).__getattribute__(name) 

顯然,正是我想要做的就是看在當前類第一,然後去BaseClass的__getattribute__爲了檢查只讀屬性。

任何幫助,非常感謝。

了一些調整.... -----------

的-----------結果讓我改變BaseFormClass __getattr____getattribute__,它似乎在BaseClass的__getattribute__之前運行,這是有道理的。

但是它導致了無限遞歸,我認爲這可能是由於BaseFormClass的__init__和BaseFormClass的子類中的某些事情發生的順序。然後,這似乎是由於__READ_ONLY被創建得太晚,我也修復了這個問題。

最終,_raw在不在BaseClass中的子類中,所以我刪除了BaseClass的_getattribute__中對_raw的任何引用。我採取了rchang的建議,將自己改爲對象,這有助於BaseClass的__getattribute__,但似乎會導致BaseFormData出現問題。我已經得到了 「得到」 部分從解釋與下面的代碼工作:

的BaseClass:

def __getattribute__(self, attr): 
     try: 
      # Default behaviour 
      return object.__getattribute__(self, attr) 
     except: 
      return self.__READ_ONLY[attr] 

BaseFormClass:

def __getattribute__(self, name): 
     # All the columns are saved in lowercase 
     lcName = s.strtolower(name) 

     try: 
      # Default behaviour 
      return object.__getattribute__(self, name) 
     except: 
      try: 
       tmp = object.__getattribute__(self, '_raw')[lcName] 
      except KeyError: 
       try: 
        tmp = BaseClass.__getattribute__(self, name) 
       except KeyError: 
        msg = "'{0}' object has no attribute '{1}'. Note that attribute search is case insensitive." 
        raise AttributeError(msg.format(type(self).__name__, name)) 

      if tmp == 'true': 
       return True 
      elif tmp == 'false': 
       return False 
      else: 
       return tmp 

諷刺的是這已經創造了另一個__getattribute__問題雖然。當我嘗試覆蓋只讀屬性時,日誌警告觸發,但在撥打self.__instanceId時失敗。這個日誌警告昨天工作正常(當我可以讓類實例化沒有錯誤)。我可以實例化類是這樣的:

a = Jan2014Lead(leadFile) 

,並獲得實例ID是這樣的:

a.__instanceId 
Out[7]: '8c08dee80ef56b1234fc4822627febfc' 

下面是實際的錯誤:

File "D:\python\lib\mybasics\mybasics\cBaseClass.py", line 255, in write2log 
    logStr = "[" + self.__instanceId + "]::[" + str(msgCode) + "]::" + msgMsg 

    File "cBaseFormData.py", line 226, in __getattribute__ 
    raise AttributeError(msg.format(type(self).__name__, name)) 

AttributeError: 'Jan2014Lead' object has no attribute '_BaseClass__instanceId'. Note that attribute search is case insensitive. 

------- ----得到它的工作,但似乎hacky .... -----------

所以上面的錯誤是從_READ_ONLY中尋找_BaseClass__instanceId但__instanceId在_READ_ONLY中。所以我簡單地修剪了傳遞的attr字符串,從頭開始刪除_BaseClass。

雖然這看起來像一個黑客。有沒有一個標準的方法來做到這一點?

+0

我不清楚你的'__READ_ONLY'字典是用於什麼。正如你現在實現'__getattribute__',只有在普通的查找失敗後纔會被檢查,這意味着(除非你有一個'__setattr__'方法你沒有提到),常規的實例變量可以分配給它們,將優先於字典的值。也許你應該單獨離開'__getattribute__'並且編寫一個'__setattr__'方法來替代某些名字。 – Blckknght 2014-12-03 06:48:16

+0

@Blckknght我已經有一個'__setattr__',當試圖覆蓋只讀attr時引發異常。我只是不認爲這與這個問題有關。我在第一週寫了BaseClass,所以我正在學習Python,'__getattribute__'和'__setattr__'的組合是我能夠按照我的預期工作的唯一方法。我可能需要重新訪問它,但現在'__setattr__'不是問題。 – 2014-12-03 17:35:50

回答

0

我不能說我曾經完全想通了這一點讓我滿意。然而,BaseClass的__getattirbute__方法所需的順序的改變,一切事都被稱爲:

def __getattribute__(self, attr): 
     if attr in self.__READ_ONLY: 
      return self.__READ_ONLY[attr] 
     else: 
      # Default behaviour 
      return object.__getattribute__(self, attr) 

然後我需要在BaseFormClass再變了__getattribute__

def __getattribute__(self, name): 
     # All the columns are saved in lowercase 
     lcName = s.strtolower(name) 

     try: 
      # Default behaviour 
      return object.__getattribute__(self, name) 
     except: 
      name = s.str_replace('_' + type(self).__name__, '', name) 
      lcName = s.strtolower(name) 

      try: 
       tmp = object.__getattribute__(self, '_raw')[lcName] 
      except KeyError: 
       #tmp = BaseClass.__getattribute__(self, name) 
       tmp = super(BaseFormData, self).__getattribute__(name) 

      if tmp == 'true': 
       return True 
      elif tmp == 'false': 
       return False 
      else: 
       return tmp 

有真的只是2點的變化這裏。最簡單的一個,我刪除了這個raise並讓BaseClass處理它。更難以描述的變化是我使用s.str_replace去除類名稱的變化。如果沒有這一行,Python通常會在_READ_ONLY字典中尋找類似_BaseClass__{some attr}的內容。但是,它僅在_READ_ONLY字典內被命名爲{some attr}。所以我把_BaseClass_部分去掉了。

顯然這是一個黑客。我不確定實現某種形式的別名不會比剝離它好。可能更明確?無論哪種方式,似乎是繞過Python的內置方法來保持屬性在類中的唯一性。

我想有一個更好的方法來做到這一點,這可能會在一年左右完全重寫,以反映更好的方式。

0

我認爲你的無限次遞歸是因爲__getattribute__試圖直接查找自己的屬性,因此導致__getattribute__在除塊以外的地方調用自己。看到這個太問題: How is the __getattribute__ method used?

也許嘗試以下(只延長使用object.__getattribute__相同的技術,您使用的是在try塊的except塊太)?

def __getattribute__(self, attr): 
     try: 
      # Default behaviour 
      return object.__getattribute__(self, attr) 
     except: 
      try: 
       # return self.__READ_ONLY[attr] 
       return object.__getattribute__(self, __READ_ONLY)[attr] 
      except KeyError: 
       lcAttr = php_basic_str.strtolower(attr) 
       # return self._raw[lcAttr] 
       return object.__getattribute__(self, _raw)[lcAttr] 
+0

我嘗試了一個簡化的版本,其中整個第二次嘗試除了塊只是成爲返回__READ_ONLY行。仍然獲得無限遞歸。這裏我真正的問題似乎是BaseClass的'__getattribute__'在BaseFormData的'__getattr__'或'__getattribute__'之前被調用。我錯過了一些東西...... – 2014-12-03 17:52:55