【问题标题】:Delay creating python dict until set/update延迟创建 python dict 直到设置/更新
【发布时间】:2017-10-09 05:53:28
【问题描述】:

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

理想情况下,我什至希望延迟创建内部字典,直到在内部字典中设置键值对。我设想对所有外部字典值使用单个 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 存储在 dict-of-dict 结构中,以便将 DelayDict 类替换为内部 dict。我知道属性可以做类似的事情,但我相信当我尝试在外部字典中替换自己时会出现同样的基本问题。

【问题讨论】:

  • 这几乎正是collections.defaultdict(dict) 会做的事情,不是吗?如果需要更改行为,您可以对其进行子类化并覆盖 __missing__ 方法。
  • defaultdict(dict) 只会延迟创建内部字典,直到创建外部键。我想添加外部键而不必创建内部字典。
  • 您需要区分没有内键的外键和根本不存在的外键吗?
  • 我不需要区分有无内键的外键。现在我为这两种类型构建了一个内部字典,一种类型是空的。我想通过使用单个延迟字典而不是所有空的内部字典来消除所有这些空字典。困难的部分是弄清楚如何使延迟字典在需要时用真正的字典替换外部字典的“值”中的自身。看来我需要存储用于获取内部字典的外部键。
  • 你不能有一个单一的内部对象占位符,因为它需要同时引用外部对象相关键,否则保留对它的引用'没有正确的行为。因此,您最终会为每个缺失值创建一个实例,并且不会比为每个缺失值创建字典节省任何内容。

标签: python dictionary properties lazy-initialization


【解决方案1】:

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

如果你被 dict 的 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 被发送到栈底。

【讨论】:

  • 我认为您的 MB 应该是 GB 在内存节省的估计。此外,dict 不会为每个条目创建一个 None 对象——它只是指向一个名为 None 的唯一对象。所以...这看起来很有帮助。我会尝试一下!谢谢
  • @dschult 您对 MB/GB 的看法是正确的!我没有意识到我的答案这么好...会编辑它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-14
  • 1970-01-01
  • 1970-01-01
  • 2016-02-24
  • 1970-01-01
相关资源
最近更新 更多