Python dict 被转储到 JSON 文件而不清除以前的文件内容

Python dict being dumped to JSON file not clearing out previous file contents

在我的数据管道结束时,当我最终将 Python 字典推送到 JSON 文件以由 API 按需拉取时,我将转储像这样对文件的字典:

json.dump(data, out_file)

99.9% 的时间都可以完美运行,并且最终用户可以以所需的格式访问数据。即:

out_file.json

{
    "good": {
        "JSON": "that", "I": "wanted", "to": ["push", ":)"]
    }, 
    "more_good": {
        "JSON": "that", "I": "wanted", "to": ["push", ":)"]
    }
}

然而,我的挣扎是其他 0.1% 的推送......我一直注意到数据将被推送而没有完全从文件中删除以前的数据,我最终会遇到这样的情况以下:

out_file.json

{
    "good": {
        "JSON": "that", "I": "wanted", "to": ["push", ":)"]
    }, 
    "more_good": {
        "JSON": "that", "I": "wanted", "to": ["push", ":)"]
    }
}ed", "to": ["push", ":)"]}}

截至目前,我想出了以下 临时 'solution':

在推送字典之前,我将推送一个空字符串来清除文件:

json.dump('', out_file)
json.dump(data, out_file)

然后,在为最终用户获取文件内容时,我将检查以确保内容可用性,如下所示:

q = json.load(in_file)
while q == '': # also acts as an if
    q = json.load(in_file)
return q

我主要担心的是,将字符串推到数据之前只会降低边缘情况的可能性(即使如此),而且我将继续看到这些相同的错误在未来发生——增加的可能性不断向数据文件发送空白字符串会破坏最终用户数据的可访问性。

由于该问题仅在 0.1% 的时间内发生,而且我不确定到底是什么导致了边缘情况,因此测试非常耗时,因此我无法确定我尝试的临时解决方案是否成功出来了。无法测试边缘情况似乎本身就是一个错误 - 由于缺乏对导致错误的原因的理解。

您没有显示 out_file 是什么或如何打开它,但我认为问题是当两个 threads/processes 尝试大致同时打开和写入文件时。文件在打开时被截断;所以如果顺序是 open1 - open2 - write1 - write2,你可能会得到类似的结果。有两个基本选择:

a) 如果另一个 thread/process 正在做同样的事情,则使用一些锁定机制来发出错误信号:互斥锁、独占访问锁...那么您将不得不处理其中一个等待的线程直到文件不再被使用,或者放弃写入。

b) 写入 named temporary file on the same filesystem, then use atomic replace.

推荐第二种,更简单也更安全

我觉得上面说的很对。您可以尝试另一种解决方案,虽然我不确定这是否适用于您的用例并且它有点 hack 而不是解决根本原因,但它是为文件创建一个唯一的 ID。

import json
from uuid import uuid4


f = str(uuid4)
with open(data, 'w') as f:
    json.dump(data, f)

但很明显,这只有在您不需要每次都调用该文件时才有效。'out_file.json'。

@Amardan 诊断出问题是由多个线程同时写入同一文件引起的,一针见血。为了解决我的特定用例中的问题,我不得不与他推荐的解决方案略有不同,甚至最终偶然合并了@osint_alex.

推荐的解决方案的元素。

不幸的是,在尝试使用@Amardan 推荐的临时文件时。尝试时我会收到以下错误:

[Errno 18] Invalid cross-device link: '/tmp' -> '/app/data/out_file.json'

这不是什么大问题,因为解决方案真正在于能够以原子方式写入我的文件,而不是使用临时文件。因此,我所要做的就是创建自己的可访问文件,在写入最终目的地之前充当数据的临时持有者。最终,我最终使用 UUID4 来命名这些临时文件,这样就不会同时写入两个文件(至少不会很快......)。最后,我实际上能够利用这个错误作为一个机会,将我所有的 'json.dump'-ing 外包给一个函数,在那里我可以测试边缘情况并确保文件一次只写入一次。最后,新函数看起来像这样:

def update_content(content, dest):
    pth = f'/app/data/{uuid.uuid4()}.json'
    with open(pth, "w") as f:
        json.dump(content, f)
    try:
        with open(pth) as f:
            q = json.load(f)
        # NOTE: edge case testing here ...
        os.replace(pth, dest)
    except: # add exceptions as you see fit
        os.remove(pth)