延迟创建python dict直到设置/更新

时间:2017-05-10 14:07:29

标签: python dictionary properties lazy-initialization

我有一个Python dict-of-dict结构,带有大量的外部字典键(数百万到数十亿)。内部dicts大多是空的,但可以存储键值对。目前我创建了一个单独的dict作为每个内部dicts。但是它使用了很多我最终没有使用的内存。每个空字典都很小,但我有很多。我想在需要之前推迟创建内部词典。

理想情况下,我甚至会延迟创建内部字典,直到内部字典中的键值对设置为止。我设想为所有外部字典值使用单个DelayDict对象。此对象就像get和 getitem 调用的空字典一样,但只要 setitem 或更新调用进来,它就会创建一个空字典来代替它。我遇到了delaydict对象知道如何将新的空dict与dict-of-dict结构连接起来的麻烦。

class DelayDict(object):    % can do much more - only showing get/set
    def __init__(self, dod):
        self.dictofdict = dod     % the outer dict
    def __getitem__(self, key):
        raise KeyError(key)
    def __setitem__(self, key, value):
        replacement = {key: value}
        % replace myself in the outer dict!!
        self.dict-of-dict[?????] = replacement

我想不出如何在dict-of-dict结构中存储新的替换字典,以便它将DelayDict类替换为内部字典。我知道属性可以做类似的事情,但我相信当我试图在外部字典中替换自己时会出现同样的根本问题。

1 个答案:

答案 0 :(得分:0)

老问题,但我遇到了类似的问题。我不确定它是不是 尝试节省一些内存的好主意,但如果你真的需要这样做,你应该尝试建立自己的数据结构。

如果你坚持使用dict的词典,这是一个解决方案。

首先,您需要一种方法来创建OuterDict中没有值的键(默认值为{})。如果OuterDict是dict __d的包装器:

def create(self, key):
    self.__d[key] = None

你会节省多少内存?

>>> import sys
>>> a = {}
>>> sys.getsizeof(a)
136

正如您所指出的,None只创建一次,但您必须对其进行引用。在Cpython(64位)中,它是8个字节。对于10亿个元素,你备用(136-8)* 10 ** 9字节= 128 Gb(而不是Mb,谢谢!)。你需要给一个 当有人要求价值时占位符。占位符跟踪外部字典和外部字典中的键。它包装了一个字典,并在分配值时将此字典分配给outer[key]

不再说话,代码:

class OuterDict():
    def __init__(self):
        self.__d = {}

    def __getitem__(self, key):
        v = self.__d[key]
        if v is None: # an orphan key
            v = PlaceHolder(self.__d, key)
        return v

    def create(self, key):
        self.__d[key] = None

class PlaceHolder():
    def __init__(self, parent, key):
        self.__parent = parent
        self.__key = key
        self.__d = {}

    def __getitem__(self, key):
        return self.__d[key]

    def __setitem__(self, key, value):
        if not self.__d:
            self.__parent[self.__key] = self.__d  # copy me in the outer dict
        self.__d[key] = value

    def __repr__(self):
        return repr("PlaceHolder for "+str(self.__d))

    # __len__, ...

测试:

o = OuterDict()
o.create("a") # a is empty
print (o["a"])

try:
    o["a"]["b"] # Key Error
except KeyError as e:
    print ("KeyError", e)

o["a"]["b"] = 2
print (o["a"])

# output:
# 'PlaceHolder for {}'
# KeyError 'b'
# {'b': 2}

为什么它不会占用太多内存?因为您没有构建数十亿个占位符。当你不再需要它们时,你会释放它们。也许你一次只需要一个。

可能的改进:您可以创建PlaceHolders的池。堆栈可能是一个很好的数据结构:最近创建的占位符可能很快就会发布。如果您需要新的PlaceHolder,那么 查看堆栈,如果占位符只有一个ref(sys.getrefcount(ph) == 1),则可以使用它。当你正在寻找时,要加快这个过程 一个免费的占位符,你可以记住最大refcount的占位符。你用这个" max refcount"切换自由占位符。占位符。因此,占位符最大 refcount被发送到堆栈的底部。