Python 中的 JSON 同步字典:处理临时引用

Nat*_*tte 5 python json python-3.x

我正在编写一个充当字典的类,但每次进行修改以确保同步状态时,都会将其内容保存到 json 文件中。

但是,我偶然发现了一个中断同步的特殊情况:将值附加到字典中的列表时。由于这使用__getitem__,我如何确保如果返回的项目被修改,我将其保存到 JSON 文件?

这是一个功能齐全的代码片段(Python 3.9.2)来说明我的意思

import json


class SyncDictJSON(dict):
    __instances: dict = {}

    @classmethod
    def create(cls, filepath: str, **kwargs):
        if filepath not in SyncDictJSON.__instances:
            SyncDictJSON.__instances[filepath] = cls(filepath, **kwargs)
        return SyncDictJSON.__instances[filepath]

    def __init__(self, filepath: str, **kwargs):
        super().__init__(**kwargs)
        self.filepath = filepath
        self.update(SyncDictJSON.read_data_from_filename(self.filepath))

    def __getitem__(self, item):
        print(f"getitem {item}")
        return super(SyncDictJSON, self).__getitem__(item)

    def __setitem__(self, key, value):
        print(f"set item {key},{value}")
        super().__setitem__(key, value)
        SyncDictJSON.write_data_to_filename(self, self.filepath)

    def __delitem__(self, key):
        super().__delitem__(key)
        SyncDictJSON.write_data_to_filename(self, self.filepath)

    @staticmethod
    def write_data_to_filename(data, filepath: str):
        with open(filepath, "w", encoding="utf-8") as file:
            json.dump(data, file, indent=2, ensure_ascii=False)

    @staticmethod
    def read_data_from_filename(filename: str):
        with open(filename, "r", encoding="utf-8") as file:
            return json.load(file)

    @classmethod
    def from_file(cls, filepath):
        return cls(filepath)


if __name__ == '__main__':
    with open("testing.json", "w") as file:
        file.write("{}")
    dico = SyncDictJSON.create("testing.json")

    dico["a_list"] = []
    dico["a_list"].append(5)

    print(dico)  # {'a_list': [5]} but testing.json will be empty

Run Code Online (Sandbox Code Playgroud)

Vir*_*ter 2

SyncDictJSON对象将记录对字典的更改。但是,一旦创建了列表a_list,向其追加内容将不会更改字典;字典将包含对 list 的引用[5],该引用仍然与该列表为空时的引用相同。

要记录附加到列表,您也可以使用以下代码,该代码将类包装list到类似的包装器中。它依赖于对传递到列表的同步字典的引用,因此需要一点额外的空间。如果您计划删除列表项和/或插入它们,您也需要覆盖 中的这些方法SyncList

import json

class SyncList(list):
    def __init__(self, container, *args, **kwargs):
        print('list init', *args, **kwargs)
        self._container = container
        super().__init__(*args, **kwargs)

    def append(self, x):
        print('list.append', self, x)
        super().append(x)
        self._container.write_data_to_filename(
            self._container, self._container.filepath)


class SyncDictJSON(dict):
    __instances: dict = {}

    @classmethod
    def create(cls, filepath: str, **kwargs):
        if filepath not in SyncDictJSON.__instances:
            SyncDictJSON.__instances[filepath] = cls(filepath, **kwargs)
        return SyncDictJSON.__instances[filepath]

    def __init__(self, filepath: str, **kwargs):
        super().__init__(**kwargs)
        self.filepath = filepath
        self.update(SyncDictJSON.read_data_from_filename(self.filepath))

    def __getitem__(self, item):
        print(f"getitem {item}")
        return super(SyncDictJSON, self).__getitem__(item)

    def __setitem__(self, key, value):
        print(f"set item {key},{value}")
        super().__setitem__(key, value)
        SyncDictJSON.write_data_to_filename(self, self.filepath)

    def __delitem__(self, key):
        super().__delitem__(key)
        SyncDictJSON.write_data_to_filename(self, self.filepath)

    @staticmethod
    def write_data_to_filename(data, filepath: str):
        with open(filepath, "w", encoding="utf-8") as file:
            json.dump(data, file, indent=2, ensure_ascii=False)

    @staticmethod
    def read_data_from_filename(filename: str):
        with open(filename, "r", encoding="utf-8") as file:
            return json.load(file)

    @classmethod
    def from_file(cls, filepath):
        return cls(filepath)


if __name__ == '__main__':
    with open("testing.json", "w") as file:
        file.write("{}")
    dico = SyncDictJSON.create("testing.json")
    dico["a_list"] = SyncList(dico, [])
    dico["a_list"].append(5)
    print(dico)  # {'a_list': [5]}; testing.json will have the same
# list init []
# set item a_list,[]
# getitem a_list
# list.append [] 5
# {'a_list': [5]}

Run Code Online (Sandbox Code Playgroud)