/EDIT:这现在是一个 Python 模块:fdict。
我遇到了类似的问题,但我正在处理嵌套字典。由于我的数据集非常嵌套,所有可用的解决方案都不适合我的用例:shelve、chest、shove、sqlite、zodb,以及任何其他 NoSQL 数据库主要在以下情况下工作你有一个级别,因为它们都腌制/JSON要序列化到数据库中的值,所以第一级之后的所有值都被序列化并且不可能对嵌套对象进行增量更新(你必须从加载整个分支一级节点,修改,然后放回去),如果嵌套对象本身很大,这是不可能的。
这意味着当您有很多 1 级节点时,这些解决方案会有所帮助,但当您有少量 1 级节点和较深的层次结构时,则不会。
为了解决这个问题,我在原生 Python dict 上创建了一个自定义 dict 类,以在内部以扁平形式表示嵌套的 dict:dict()['a']['b'] 将在内部表示为 dict()['a/b']。
然后在这个“内部扁平化”的字典之上,您现在可以使用任何对象到磁盘的解决方案,例如 shelve,这是我使用的,但您可以轻松地用其他东西替换。
代码如下:
import shelve
class fdict(dict):
'''Flattened nested dict, all items are settable and gettable through ['item1']['item2'] standard form or ['item1/item2'] internal form.
This allows to replace the internal dict with any on-disk storage system like a shelve's shelf (great for huge nested dicts that cannot fit into memory).
Main limitation: an entry can be both a singleton and a nested fdict, and there is no way to tell what is what, no error will be shown, the singleton will always be returned.
'''
def __init__(self, d=None, rootpath='', delimiter='/', *args):
if d:
self.d = d
else:
self.d = {}
self.rootpath = rootpath
self.delimiter = delimiter
def _buildpath(self, key):
return self.rootpath+self.delimiter+key if self.rootpath else key
def __getitem__(self, key):
# Node or leaf?
if key in self.d: # Leaf: return the value
return self.d.__getitem__(key)
else: # Node: return a new full fdict based on the old one but with a different rootpath to limit the results by default
return fdict(d=self.d, rootpath=self._buildpath(key))
def __setitem__(self, key, value):
self.d.__setitem__(self._buildpath(key), value)
def keys(self):
if not self.rootpath:
return self.d.keys()
else:
pattern = self.rootpath+self.delimiter
lpattern = len(pattern)
return [k[lpattern:] for k in self.d.keys() if k.startswith(pattern)]
def items(self):
# Filter items to keep only the ones below the rootpath level
if not self.rootpath:
return self.d.items()
else:
pattern = self.rootpath+self.delimiter
lpattern = len(pattern)
return [(k[lpattern:], v) for k,v in self.d.items() if k.startswith(pattern)]
def values(self):
if not self.rootpath:
return self.d.values()
else:
pattern = self.rootpath+self.delimiter
lpattern = len(pattern)
return [v for k,v in self.d.items() if k.startswith(pattern)]
def update(self, d2):
return self.d.update(d2.d)
def __repr__(self):
# Filter the items if there is a rootpath and return as a new fdict
if self.rootpath:
return repr(fdict(d=dict(self.items())))
else:
return self.d.__repr__()
def __str__(self):
if self.rootpath:
return str(fdict(d=dict(self.items())))
else:
return self.d.__str__()
class sfdict(fdict):
'''A nested dict with flattened internal representation, combined with shelve to allow for efficient storage and memory allocation of huge nested dictionnaries.
If you change leaf items (eg, list.append), do not forget to sync() to commit changes to disk and empty memory cache because else this class has no way to know if leaf items were changed!
'''
def __init__(self, *args, **kwargs):
if not ('filename' in kwargs):
self.filename = None
else:
self.filename = kwargs['filename']
del kwargs['filename']
fdict.__init__(self, *args, **kwargs)
self.d = shelve.open(filename=self.filename, flag='c', writeback=True)
def __setitem__(self, key, value):
fdict.__setitem__(self, key, value)
self.sync()
def get_filename(self):
return self.filename
def sync(self):
self.d.sync()
def close(self):
self.d.close()
fdict 和 sfdict 都可以像标准的dict 一样使用(但我没有实现所有方法,呵呵)。
完整代码在这里:
https://gist.github.com/lrq3000/8ce9174c1c7a5ef546df1e1361417213
这是在完整的 Python 模块中进一步开发的:fdict。
基准测试后,使用间接访问(即x['a']['b']['c'])时,这比字典慢大约 10 倍,而使用直接访问(即x['a/b/c'])时,速度大约一样快,尽管这里我不考虑shelve 保存到 anydbm 文件的开销,与 dict 相比,只是 fdict 数据结构的开销。