有没有理由不使用OrderedDict?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有没有理由不使用OrderedDict?相关的知识,希望对你有一定的参考价值。
我指的是来自OrderedDict模块的collections
,这是一个有序的字典。
如果它具有可订购的附加功能,我意识到这可能通常不是必要的,但即便如此,是否有任何缺点?它慢了吗?它缺少任何功能吗?我没有看到任何遗漏的方法。
简而言之,为什么我不应该总是使用它而不是普通的字典呢?
OrderedDict
是dict
的子类,需要更多内存来跟踪添加键的顺序。这不是微不足道的。该实现在封面下添加了第二个dict
,以及所有键的双重链接列表(这是记住订单的部分),以及一堆弱反射代理。它并没有慢很多,但至少使用普通的dict
使内存翻倍。
但如果合适,请使用它!这就是为什么它在那里:-)
这个怎么运作
基本字典只是一个普通的字典映射键值 - 它根本不是“有序”的。当添加<key, value>
对时,key
将附加到列表中。列表是记住订单的部分。
但如果这是一个Python列表,删除一个密钥将需要两次O(n)
时间:O(n)
时间在列表中找到密钥,O(n)
时间从列表中删除密钥。
所以这是一个双向链表。这使得删除一个关键常数(O(1)
)时间。但是我们仍然需要找到属于密钥的双向链表节点。为了使O(1)
时间运行,第二个 - 隐藏 - 字典将键映射到双向链表中的节点。
因此,添加新的<key, value>
对需要将该对添加到基本字典,创建一个新的双向链表节点来保存密钥,将新节点附加到双向链表,并将密钥映射到隐藏的新节点字典。两倍多的工作,但仍然O(1)
(预期案例)时间整体。
同样,删除当前存在的密钥也是工作量的两倍,但是O(1)
整体预期时间:使用隐藏的字典找到密钥的双向链表节点,从列表中删除该节点,并从两个字符串中删除密钥。
等等。效率很高。
多线程
如果您的字典是从没有锁定的多个线程访问的,尤其是作为同步点。
vanilla dict操作是原子的,而在Python中扩展的任何类型都不是。
事实上,我甚至不确定OrderedDict是线程安全的(没有锁定),虽然我不能忽视它是非常仔细编码并满足重入的定义的可能性。
较小的恶魔
如果您创建大量这些词典,则使用内存
cpu用法,如果您的所有代码都是这些字典
为什么我不应该总是使用它而不是普通的字典
在Python 2.7中,正常的OrderedDict
用法将创建参考周期。因此,任何使用OrderedDict
都需要启用垃圾收集器才能释放内存。是的,垃圾收集器默认在cPython中打开,但禁用它has its uses。
例如使用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']
即使您只是创建一个空的OrderedDict
(d = collections.OrderedDict()
)并且不添加任何内容,或者您明确尝试通过调用clear
方法(d.clear()
之前的del d
)来清理它,您仍然会得到一个自引用列表:
gc: collectable <list 0000000003ABBA08>
0 [[...], [...], None]
这似乎就是这种情况,因为this commit删除了__del__
方法,以防止OrderedDict
导致无法收集的循环的可能性,这可能更糟。如该提交的更改日志中所述:
Issue #9825:从collections.OrderedDict的定义中删除了__del__。这可以防止用户创建的自引用有序词典成为永久无法收集的GC垃圾。缺点是删除__del__意味着内部双向链表必须等待GC收集,而不是在refcnt降为零时立即释放内存。
请注意,在Python 3中,针对同一问题的fix采用了不同的方法,并使用weakref代理来避免循环:
问题#9825:在collections.OrderedDict的定义中使用__del__使用户可以创建自引用的有序字典,这些字典成为永久无法收集的GC垃圾。恢复了使用weakref代理的Py3.1方法,这样就不会首先创建引用循环。
从Python 3.7开始,所有字典都保证订购。 Python贡献者确定切换到订购dict
不会对性能产生负面影响。我不知道OrderedDict
的性能与Python中的dict
相比> = 3.7,但我认为它们是可比较的,因为它们都是有序的。
也可以看看:
以上是关于有没有理由不使用OrderedDict?的主要内容,如果未能解决你的问题,请参考以下文章
通过索引访问 collections.OrderedDict 中的项目