有没有理由不使用OrderedDict?

时间:2013-09-23 02:56:25

标签: python dictionary python-3.x ordereddictionary

我指的是collections模块中的OrderedDict,这是一个有序字典。

如果它具有可订购的附加功能,我意识到可能通常没有必要,但即便如此,是否有任何缺点?它慢了吗?它缺少任何功能吗?我没有看到任何遗漏的方法。

简而言之,为什么不应该我总是使用它而不是普通的字典?

4 个答案:

答案 0 :(得分:123)

OrderedDictdict的子类,需要更多内存来跟踪添加键的顺序。这不是微不足道的。该实现在封面下添加了第二个dict,以及所有键的双重链接列表(这是记住订单的部分),以及一堆弱反射代理。它不是很多慢,但至少使用普通dict使内存翻倍。

但如果合适,请使用它!这就是为什么它在那里: - )

如何运作

基本字典只是一个普通的字典映射键值 - 它根本不是“有序”的。添加<key, value>对后,key会附加到列表中。列表是记住订单的部分。

但如果这是一个Python列表,删除一个密钥需要O(n)次两次:O(n)时间来查找列表中的密钥,{{1时间从列表中删除密钥。

所以这是一个双向链表。这使得删除键常量(O(n))时间。但是我们仍然需要找到属于密钥的双向链表节点。为了使该操作O(1)时间,第二个 - 隐藏 - 字典将键映射到双向链表中的节点。

因此,添加新的O(1)对需要将该对添加到基本dict,创建一个新的双向链接列表节点来保存密钥,将新节点附加到双向链接列表,并映射密钥到隐藏字典中的新节点。工作量的两倍多,但总体上仍为<key, value>(预期的情况)时间。

同样,删除当前存在的密钥也是工作量的两倍,但整体预期时间为O(1):使用隐藏字典查找密钥的双向链表节点,从列表中删除该节点,并从两个dicts中删除密钥。

等。这非常有效。

答案 1 :(得分:7)

<强>多线程

如果您的字典是从没有锁定的多个线程访问的,尤其是作为同步点。

vanilla dict操作是原子操作,而在Python中扩展的任何类型都不是。

事实上,我甚至不确定OrderedDict是否是线程安全的(没有锁定) 我不能忽视它经过仔细编码并满足重入定义的可能性。

较小的恶魔

如果您创建大量这些词典,则会占用内存

cpu用法,如果您的所有代码都是munge这些词典

答案 2 :(得分:3)

  

为什么我不应该总是使用它而不是普通字典

在Python 2.7中,正常OrderedDict用法将创建参考周期。因此,OrderedDict的任何使用都需要启用垃圾收集器才能释放内存。是的,垃圾收集器默认在cPython中打开,但禁用它has its uses

e.g。使用cPython 2.7.14

from __future__ import print_function

import collections
import gc

if __name__ == '__main__':
    d = collections.OrderedDict([('key', 'val')])
    gc.collect()
    del d
    gc.set_debug(gc.DEBUG_LEAK)
    gc.collect()
    for i, obj in enumerate(gc.garbage):
        print(i, obj)

输出

gc: collectable <list 00000000033E7908>
gc: collectable <list 000000000331EC88>
0 [[[...], [...], 'key'], [[...], [...], 'key'], None]
1 [[[...], [...], None], [[...], [...], None], 'key']

即使您只是创建一个空的OrderedDictd = collections.OrderedDict())并且不向其添加任何内容,或者您​​明确尝试通过调用clear方法来清理它({在d.clear()之前{1}},您仍会获得一个自引用列表:

del d

由于this commit已移除gc: collectable <list 0000000003ABBA08> 0 [[...], [...], None] 方法,以防止__del__可能导致无法收集的周期,这似乎更糟糕。如该提交的更改日志中所述:

  

Issue #9825:从collections.OrderedDict的定义中删除了__del__。   这可以防止用户创建的自引用有序词典   成为永久无法收集的GC垃圾。缺点是   删除__del__意味着内部双向链表必须等待   GC收集而不是在refcnt下降时立即释放内存   为零。

请注意,在Python 3中,针对同一问题的fix采用了不同的方法,并使用弱参数来避免循环:

  

问题#9825:在collections.OrderedDict定义中使用__del__   用户可以创建自引用的有序词典   这成为永久无法收集的GC垃圾。恢复了Py3.1   使用weakref代理的方法,以便永远不会创建引用循环   首先。

答案 3 :(得分:1)

自Python 3.7起,保证所有字典都是有序的。 Python贡献者确定切换为按dict排序不会对性能产生负面影响。我不知道OrderedDict的性能与Python> = 3.7中的dict相比如何,但是我想它们是可比的,因为它们都是有序的。

另请参阅: