【问题标题】:OrderedDict vs defaultdict vs dict [closed]OrderedDict vs defaultdict vs dict [关闭]
【发布时间】:2013-10-28 08:02:05
【问题描述】:

在 python 的库中,我们现在有两个 Python 实现的字典,它们在原生 dict 类型之上继承了 dict 的子类。

Python 的拥护者总是更喜欢defaultdict,而不是尽可能使用dict.setdefault。甚至 doc 引用 This technique is simpler and faster than an equivalent technique using dict.setdefault():

类似地,由于字典不保持顺序,使用OrderedDict 而不是使用dict 然后对项目进行排序是最好的替代用法。

在上述两种情况下,代码肯定更干净,但代价是性能损失。

在回答和评论其中一个问题 python unique list based on item 时,我偶然发现了在使用 defaultdictOrderedDict 时对原生 dict 的性能损失。似乎数据的大小对于dict 解决方案相对于其他解决方案的性能优势也不是无关紧要的。

我相信There should be one-- and preferably only one --obvious way to do it.,那么首选的方式是什么?

【问题讨论】:

  • defaultdict 不一定比普通的dict 慢。时间安排有缺陷,因为时间安排包括创建对象。除此之外,还有不同类型的性能,易于维护就是其中之一。您没有指定任何衡量绩效的标准。只需使用适合工作的工具
  • 再次重申:为您的用例制定自己的时间安排。您引用的时间是针对小型数据集的,包括创建字典对象;因为 dict 文字 ({}) 的创建速度比 defaultdict(...) 工厂调用(全局查找、堆栈推送、调用)更快地创建,后者会在小数据集上不公平地扭曲结果。 defaultdict 较慢的假设是有缺陷的。
  • 虽然我同意defaultdict 通常比必须调用dict.setdefault 更可取,但为什么OrderedDict 会“尽可能首选”而不是dict?我认为我从来没有关心过将键插入字典的顺序,这是一个比简单地为新键提供默认值更具体的功能。

标签: python dictionary


【解决方案1】:

没有一个单一的答案,也没有一个真实且唯一的字典。在众多变量中,它取决于:

  1. 数据集的大小;
  2. 唯一键的数量与数据映射集中重复键的数量;
  3. defaultdict 底层工厂的速度;
  4. OrderDict 的速度与稍后的一些订购步骤;
  5. Python 版本。

讨厌概括,但这里有一些概括:

  1. This technique is simpler and faster than an equivalent technique using dict.setdefault() 声明完全错误。这取决于数据;
  2. setdefault 使用小数据集更快、更简单;
  3. defaultdict 对于具有更多同质键集的较大数据集(即,添加元素后字典的长度)更快;
  4. setdefault 具有更多异构密钥集的优势;
  5. 这些结果对于 Python 3 和 Python 2 是不同的;
  6. OrderedDict 在所有情况下都较慢,除了依赖于顺序的算法并且顺序不易重构或排序;
  7. 对于大多数 dict 操作而言,Python 3 通常更快;
  8. Python 3.6 的字典现在按插入顺序排序(降低了OrderedDict 的用处)。

唯一的事实:视情况而定!这三种技术都很有用。

下面是一些计时代码:

from __future__ import print_function
from collections import defaultdict
from collections import OrderedDict

try:
    t=unichr(100)
except NameError:
    unichr=chr

def f1(li):
    '''defaultdict'''
    d = defaultdict(list)
    for k, v in li:
        d[k].append(v)
    return d.items()

def f2(li):
    '''setdefault'''
    d={}
    for k, v in li:
        d.setdefault(k, []).append(v)
    return d.items()

def f3(li):
    '''OrderedDict'''
    d=OrderedDict()
    for k, v in li:
        d.setdefault(k, []).append(v)
    return d.items()      


if __name__ == '__main__':
    import timeit
    import sys
    print(sys.version)
    few=[('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
    fmt='{:>12}: {:10.2f} micro sec/call ({:,} elements, {:,} keys)'
    for tag, m, n in [('small',5,10000), ('medium',20,1000), ('bigger',1000,100), ('large',5000,10)]:
        for f in [f1,f2,f3]:
            s = few*m
            res=timeit.timeit("{}(s)".format(f.__name__), setup="from __main__ import {}, s".format(f.__name__), number=n)
            st=fmt.format(f.__doc__, res/n*1000000, len(s), len(f(s)))
            print(st)
            s = [(unichr(i%0x10000),i) for i in range(1,len(s)+1)]
            res=timeit.timeit("{}(s)".format(f.__name__), setup="from __main__ import {}, s".format(f.__name__), number=n)
            st=fmt.format(f.__doc__, res/n*1000000, len(s), len(f(s)))
            print(st)            
        print() 

Python 2.7 结果:

2.7.5 (default, Aug 25 2013, 00:04:04) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)]
 defaultdict:      10.20 micro sec/call (25 elements, 3 keys)
 defaultdict:      21.08 micro sec/call (25 elements, 25 keys)
  setdefault:      13.41 micro sec/call (25 elements, 3 keys)
  setdefault:      18.24 micro sec/call (25 elements, 25 keys)
 OrderedDict:      49.47 micro sec/call (25 elements, 3 keys)
 OrderedDict:     102.16 micro sec/call (25 elements, 25 keys)

 defaultdict:      28.28 micro sec/call (100 elements, 3 keys)
 defaultdict:      79.78 micro sec/call (100 elements, 100 keys)
  setdefault:      45.68 micro sec/call (100 elements, 3 keys)
  setdefault:      68.66 micro sec/call (100 elements, 100 keys)
 OrderedDict:     117.78 micro sec/call (100 elements, 3 keys)
 OrderedDict:     343.17 micro sec/call (100 elements, 100 keys)

 defaultdict:    1123.60 micro sec/call (5,000 elements, 3 keys)
 defaultdict:    4250.44 micro sec/call (5,000 elements, 5,000 keys)
  setdefault:    2089.86 micro sec/call (5,000 elements, 3 keys)
  setdefault:    3803.03 micro sec/call (5,000 elements, 5,000 keys)
 OrderedDict:    4399.16 micro sec/call (5,000 elements, 3 keys)
 OrderedDict:   16279.14 micro sec/call (5,000 elements, 5,000 keys)

 defaultdict:    5609.39 micro sec/call (25,000 elements, 3 keys)
 defaultdict:   25351.60 micro sec/call (25,000 elements, 25,000 keys)
  setdefault:   10267.00 micro sec/call (25,000 elements, 3 keys)
  setdefault:   24091.51 micro sec/call (25,000 elements, 25,000 keys)
 OrderedDict:   22091.98 micro sec/call (25,000 elements, 3 keys)
 OrderedDict:   94028.00 micro sec/call (25,000 elements, 25,000 keys)

Python 3.3 结果:

3.3.2 (default, May 21 2013, 11:50:47) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))]
 defaultdict:       8.58 micro sec/call (25 elements, 3 keys)
 defaultdict:      21.18 micro sec/call (25 elements, 25 keys)
  setdefault:      10.42 micro sec/call (25 elements, 3 keys)
  setdefault:      14.58 micro sec/call (25 elements, 25 keys)
 OrderedDict:      45.43 micro sec/call (25 elements, 3 keys)
 OrderedDict:      92.69 micro sec/call (25 elements, 25 keys)

 defaultdict:      20.47 micro sec/call (100 elements, 3 keys)
 defaultdict:      77.48 micro sec/call (100 elements, 100 keys)
  setdefault:      34.22 micro sec/call (100 elements, 3 keys)
  setdefault:      54.86 micro sec/call (100 elements, 100 keys)
 OrderedDict:     107.37 micro sec/call (100 elements, 3 keys)
 OrderedDict:     318.98 micro sec/call (100 elements, 100 keys)

 defaultdict:     714.70 micro sec/call (5,000 elements, 3 keys)
 defaultdict:    3892.92 micro sec/call (5,000 elements, 5,000 keys)
  setdefault:    1502.91 micro sec/call (5,000 elements, 3 keys)
  setdefault:    2888.08 micro sec/call (5,000 elements, 5,000 keys)
 OrderedDict:    3912.95 micro sec/call (5,000 elements, 3 keys)
 OrderedDict:   14863.02 micro sec/call (5,000 elements, 5,000 keys)

 defaultdict:    3649.02 micro sec/call (25,000 elements, 3 keys)
 defaultdict:   22313.17 micro sec/call (25,000 elements, 25,000 keys)
  setdefault:    7447.28 micro sec/call (25,000 elements, 3 keys)
  setdefault:   18426.88 micro sec/call (25,000 elements, 25,000 keys)
 OrderedDict:   19202.17 micro sec/call (25,000 elements, 3 keys)
 OrderedDict:   85946.45 micro sec/call (25,000 elements, 25,000 keys)

【讨论】:

  • "defaultdict 对于 ... 更同质的密钥集更快"。这里的“同质”是什么意思?您是指类型的同质性吗?
  • “同质性”是指添加一些可能有重复的元素后有多少键。如果您这样做dict(zip('123','abc')),您最终会从 3 个添加中获得 3 个键 - 0 同质性;如果你这样做dict(zip('111','abc')),你最终会得到一个 dict,其中包含来自三个添加的 1 个键 - 非常同质。
  • @dawg 我怀疑如果您在setdefault 中使用list() 而不是[],则在多键版本中差异实际上会消失。如果您不能在 .setdefault 中使用文字,这可能是相关的,例如与set 对象
  • @juanpa.arrivillaga 我将其添加到基准测试中,是的,它们更接近但并不显着......
【解决方案2】:

我觉得你的假设 - 只有一种更可取的方式 - 不成立。我看到至少两个具有不同要求的案例:

维护密集型代码(例如,不断发展的实用程序类的选项解析器)中,我总是会选择更简洁的代码,以便我和其他人可以实现新功能更容易。性能并不重要,因为只处理少量(例如用户设置的字典)。

性能关键算法在数据处理任务中的实现,我不介意编写一点更详细的代码以更快地执行。如果算法不太可能改变,那么可读性差的代码就不会成为问题。

【讨论】:

  • the implementation of a performance-critical algorithm in a data processing task, I would not mind writing a bit more verbose code for much faster execution?在这种情况下,为什么不使用本机代码编写实现呢? only one preferable way .. 这不是我的假设,而是 Tim Peters 的指导方针。
  • 对于给定的、完全指定的用例(以及完全已知的边界条件),可能可以选择一个更好的方式。但我认为这些情况很少见。大多数时候,必须进行一些权衡(性能、可读性、通用性……),而更可取的方法基本上是主观选择具有最少感知缺点的选项。跨度>
  • 其中一些只是投资回报率。用 Cython 或 PyPy 编写它的复杂性比普通 python 高,编译 C 库仍然更复杂。您可以决定折衷中途(更快的本机 Python 解决方案)是值得的,而更远的折衷(本机库)则不值得。
猜你喜欢
  • 2015-03-04
  • 2012-06-03
  • 2013-06-10
  • 1970-01-01
  • 2014-10-17
  • 1970-01-01
  • 2023-04-09
  • 2011-07-08
  • 2017-06-20
相关资源
最近更新 更多