【问题标题】:JSON synchronized Dictionary in Python : dealing with temporary referencesPython 中的 JSON 同步字典:处理临时引用
【发布时间】:2021-04-03 16:21:48
【问题描述】:

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

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

这是一个功能齐全的代码 sn-p (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

【问题讨论】:

  • 我很好奇是否有一种聪明的方法可以实现这一点,但是您无法随时控制某人更改列表对象,该列表对象也恰好被引用为容器中的值.如果一个可变对象存储在您的容器中,也许您可​​以捕捉到,您可以为它配备一些通知容器类的事件机制(递归地,如果可变对象本身包含可变对象,如列表列表)。
  • 您的代码无法与 mw 一起使用(不知道为什么)随机更改会产生正确的输出,但不知道如何。
  • 不过还是试试 dico["a_list"] = 5 看看是否可行
  • @pipo1980 对我来说,这段代码以退出代码 0 结束。 dico["a_list"] = 5 将起作用,但问题是我希望更改字典中的对象以触发保存。如果有人想要挑战,你上面的评论似乎是一个好的开始
  • 对我来说 dico["a_list"] = 5 作品。我的意思是我有一个空键的 json 文件,其中填充了 dico["a_list"] = 5,问题是你的工作代码不起作用并给出 FileNotFoundError: [Errno 2] No such file or directory: 'testing.json'如果没有文件或 JSONDecodeError: Expecting value error if no file is present or JSONDecodeError: Expecting value error if no file is present or JSONDecodeError: Expecting value error if no file is present or JSONDecodeError: error if no file is present or JSONDecodeError: error if no file is present or JSONDecodeError: Error if no file is present or JSONDecodeError: Error if no file is present 错误,因此如果您不向我们提供我们无法测试您的代码的起始 json 文件,则“功能齐全的代码 sn-p”将不起作用

标签: python json python-3.x


【解决方案1】:

SyncDictJSON 对象将记录对字典的更改。然而,有一次 a_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]}

【讨论】:

  • 这是个好主意,我认为它总结了答案。正如 V 博士所说,最好的实现是对所有可变类型执行此操作,但这应该对我有用:D
  • @VirtualScooter ,我一直在玩子类 dict 试图理解最初的问题。在我的代码中,我可以触发将修改后的字典保存到文件中,只需使用:print(dico['a_list'])。但仍然无法理解为什么 dico[''a_list'].append(7) 不会触发 getitem 或 setitem 在我的情况下将数据转储到 json 文件。好的,我得到“字典将包含对列表 [5] 的引用,该引用仍然与该列表为空时的引用相同”但是是否有任何类型的字典方法可以在追加后调用刷新此引用?
  • @pippo1980 dico['a_list'].append(5) does 触发get item,问题是get item在执行dico['a_list']的时候被触发,所以在append之前,所以item还在一样的
  • 好的,谢谢我现在更清楚了。因此,让我的 JSON 不断更新的唯一方法是在 dict 本身发生任何更改后调用 dict 键值?
猜你喜欢
  • 2018-12-07
  • 2016-05-10
  • 2013-02-07
  • 1970-01-01
  • 2018-10-01
  • 1970-01-01
  • 2011-02-25
  • 1970-01-01
  • 2012-09-20
相关资源
最近更新 更多