将Python字典列表追加到文件中而无需加载文件

15

假设我需要一个由字典列表组成的数据库文件:

文件:

[
  {"name":"Joe","data":[1,2,3,4,5]},
  {   ...                         },
           ...
]

我需要一个函数,接收上述形式的字典列表,并将其附加到文件中。有没有办法实现这一点,比如使用json(或任何其他方法),而不必加载整个文件?

编辑1: 注意:我需要将新的字典附加到磁盘上已经存在的文件中。


“Without loading it”是什么意思? - user2357112
1
有一种方法是将文件加载到内存中,将新列表附加到其中,然后将结果转储回磁盘。是否可能只将新列表写入磁盘,将其附加到文件末尾,而无需将文件加载到内存中? - jazzblue
这可能会有用:https://dev59.com/z3vfs4cB2Jgan1znwcdw将新字典加载到一个新文件中,然后合并这两个文件,也许是一种方法? - Jeremy Kalas
4个回答

31
您可以使用JSON将字典转储为每行一个的格式。现在每行都是一个您编写的单个JSON字典。您会失去外部列表,但可以通过简单的追加到现有文件中添加记录。
import json
import os

def append_record(record):
    with open('my_file', 'a') as f:
        json.dump(record, f)
        f.write(os.linesep)

# demonstrate a program writing multiple records
for i in range(10):
    my_dict = {'number':i}
    append_record(my_dict)

列表可以稍后组装

with open('my_file') as f:
    my_list = [json.loads(line) for line in f]

这个文件看起来像是

{"number": 0}
{"number": 1}
{"number": 2}
{"number": 3}
{"number": 4}
{"number": 5}
{"number": 6}
{"number": 7}
{"number": 8}
{"number": 9}

这里看起来你实际上并没有将字典追加到磁盘上的现有文件中,而是在代码中创建了所有的字典并将它们写入文件。我需要的是将它们追加到现有文件中。我应该在我的原始问题中注明这一点。 - jazzblue
不,它正在按照您的要求将内容附加到文件中。for循环只是一个演示程序,可以多次向文件附加记录。运行演示两次,您会在末尾获得更多记录。我会进行编辑以使其更加清晰易懂。 - tdelaney
1
如果您不想使用漂亮的 JSON(如果想要组装部分更难),这是一个不错的解决方案。 - saeedgnu
@ilius - 是的,记录文件本来就不应该很漂亮!这实际上是一个关于你想要使用什么作为记录分隔符的问题。如果你不进行漂亮打印,那么json就不会添加任何新行,这是一个很好的分隔符(这就是我在这里所做的)。如果你想要漂亮打印,你可以选择像'\n---\n'这样的东西,但你必须自己扫描并进行记录阻塞。 - tdelaney

9
如果需要保持文件有效的 JSON 格式,可以按照以下步骤进行操作:
import json

with open (filepath, mode="r+") as file:
    file.seek(0,2)
    position = file.tell() -1
    file.seek(position)
    file.write( ",{}]".format(json.dumps(dictionary)) )
这将打开文件,同时允许读写操作。然后,它会移动到文件末尾(距离文件开头的零字节),以确定文件末尾的位置(相对于文件开头),并向后移动一个字节,这在json文件中应该代表字符]。最后,它将新的字典附加到结构中,覆盖文件的最后一个字符,并保持其为有效的json格式。它不会将文件读入内存。已在Python 3.4.3中测试了ANSI和utf-8编码的文件,包括小型和大型(5 GB)虚拟文件。 如果您还导入了os模块,则可以使用以下变体:
import os, json

with open (filepath, mode="r+") as file:
    file.seek(os.stat(filepath).st_size -1)
    file.write( ",{}]".format(json.dumps(dictionary)) )

它定义了文件的字节长度,以便到达比当前位置少一个字节的位置(与上一个示例中类似)。


2
如果您不想实际“加载”文件,使用“json”并不是正确的方法。您可以使用内存映射文件...而且从未将文件加载到内存中--memmap数组可以打开文件并构建一个“在磁盘上”的数组,而不将任何内容加载到内存中。 创建一个字典的内存映射数组:
>>> import numpy as np
>>> a = np.memmap('mydict.dat', dtype=object, mode='w+', shape=(4,))
>>> a[0] = {'name':"Joe", 'data':[1,2,3,4]}
>>> a[1] = {'name':"Guido", 'data':[1,3,3,5]}
>>> a[2] = {'name':"Fernando", 'data':[4,2,6,9]}
>>> a[3] = {'name':"Jill", 'data':[9,1,9,0]}
>>> a.flush()
>>> del a

现在读取数组,而不加载文件:

>>> a = np.memmap('mydict.dat', dtype=object, mode='r')

当列表被创建时,文件的内容会被加载到内存中,但这并非必须的 -- 您可以在不加载它的情况下直接使用磁盘上的数组。

>>> a.tolist()
[{'data': [1, 2, 3, 4], 'name': 'Joe'}, {'data': [1, 3, 3, 5], 'name': 'Guido'}, {'data': [4, 2, 6, 9], 'name': 'Fernando'}, {'data': [9, 1, 9, 0], 'name': 'Jill'}]

创建一个可以索引文件的内存映射数组只需要极少量的时间(例如几纳秒),无论文件大小(例如100 GB)。


0

使用与user3500511相同的方法...

假设我们有两个字典列表(dicts, dicts2)。将dicts转换为JSON格式的字符串。将dicts保存到新文件test.json中。重新打开test.json并使用合适的分隔符格式化字符串对象。使用重新格式化的对象,可以附加dict2,并且文件仍然保持JSON对象的正确结构。

import json

dicts = [{ "name": "Stephen", "Number": 1 }
         ,{ "name": "Glinda", "Number": 2 }
         ,{ "name": "Elphaba", "Number": 3 }
         ,{ "name": "Nessa", "Number": 4 }]

dicts2= [{ "name": "Dorothy", "Number": 5 }
         ,{ "name": "Fiyero", "Number": 6 }]


f = open("test.json","w")
f.write(json.dumps(dicts))
f.close()

f2 = open("test.json","r+")
f2.seek(-1,2)
f2.write(json.dumps(dicts2).replace('[',',',1))
f2.close()

f3 = open('test.json','r')
f3.read()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,