2017-02-14 42 views
0

所以我子類化一個Python dict,大致是這樣的:Synchonize屬性更改在Python

class JSONDict(dict): 
    @property 
    def __json__(self): 
     return json.dumps(self) 

    @property 
    def __path__(self): 
     return os.path.join(settings.BASE_PATH, self.entity, 'settings.json') 

    def __init__(self, entity, *args, **kwargs): 
     dict.__init__(self) 
     self.entity = entity 

     try: 
      os.utime(self.__path__, None) 
     except OSError: 
      open(self.__path__, 'a').close() 

     self.__file__ = open(self.__path__, 'r+') 
     self.__to_python__() 

    def __enter__(self): 
     return self 

    def __exit__(self, exc_type, exc_value, traceback): 
     self.__file__.close() 

    def __to_json__(self): 
     self.__file__.seek(0) 
     self.__file__.write(self.__json__) 
     self.__file__.flush() 
     self.__file__.truncate() 

    def __to_python__(self): 
     if os.path.getsize(self.__path__): 
      self.__file__.seek(0) 
      dict.clear(self) 
      dict.update(self, json.load(self.__file__)) 
     else: 
      self.clear() 

    def __getitem__(self, key): 
     self.__to_python__() 
     return dict.__getitem__(self, key) 

    def __setitem__(self, key, value): 
     dict.__setitem__(self, key, value) 
     self.__to_json__() 

    def update(self, data): 
     dict.update(self, data) 
     self.__to_json__() 

    def clear(self): 
     dict.clear(self) 
     self.update(__default__) 
     self['nar'] = self.nar 

我可以用這個相當精細,例如:

>>> with JSONDict('entity-id') as json_info: 
... json_info['setting_a'] = 'foo' 

然而,有一個有趣的問題當涉及到名單。當我直接修改列表時,例如json_info['setting_b'].append('bar'),JSON文件不會被更新。我肯定覆蓋__setitem____getitem__將涵蓋所有項目訪問,但顯然情況並非如此。我應該重寫什麼來完成這項工作?

UPDATE:

讀了第一的答案,我覺得這個問題是不是在正確的方式制定。正確的問題是,如何有效地觀察字典屬性的變化,以便能夠相應地更新JSON?應該通過代理/描述符對象,元類來完成,還是有更通用的方法?

+0

'列表'只是一個'列表'。 –

+2

注意,你真的不應該定義你自己的雙下劃線方法。只需將它們稱爲'to_json','to_python'等。 –

+2

關鍵是,列表的**引用不會改變,所以'JSONDict'我們不知道其發生在其子節點(和子節點一般)。沒有向父母報告兒童的更新。 –

回答

2

正如威廉·Onsem提到,變異一個list不會更新字典本身 - 做json_info['some_list'].append(whatever)當你不打電話JSONDict.__setitem__ - 實際上你就會有同樣的問題,只是任何可變對象。

的技術方案,這裏將是使__getitem__回報代理包裝的實際對象和JSONDict實例和壓倒一切的__setattr__檢測代理對象上的任何改變,即:

class Proxy(object): 
    def __init__(self, jsdict, key, obj): 
     # avoids triggering `__setattr__` 
     self.__dict__["__jsdict"] = jsdict 
     self.__dict__["__key"] = key 
     self.__dict__["__obj"] = obj 

    def __setattr__(self, name, value): 
     setattr(self.__obj, name, value) 
     self.__jsdict[self.__key] = self.__obj 

    def __getattr__(self, name): 
     return getattr(self.__obj, name) 


class JSONDict(dict): 
    # ... 

    def __getitem__(self, key): 
     self.__to_python__() 
     return Proxy(self, key, dict.__getitem__(self, key)) 

現在,這仍然無法正常工作列表(或其他容器),所以你也需要實現Proxy.__setitem__。並且由於您的JSONDict中包含的列表可以包含可變對象,所以您需要確保您還可以代理JSONDict包含的任何容器...

哦,是的,無論在代理對象上查找屬性,都應該代理也是如此,json_info['some_list'][42].foo.baaz = 'wtf'按預期工作。

好的我認爲你現在已經明白了:以通用,可靠的方式進行這項工作將會很困難。更不用說表演擊中了...

你可能會有一個更可靠的解決方案,只需將文件保存到__exit__和/或爲最終用戶提供明確的save()方法。這也可以節省(沒有雙關語)磁盤寫入...