OrderedDict vs defaultdict vs dict

时间:2013-10-28 08:02:06

标签: python dictionary

在python的库中,我们现在有两个字典的Python实现,它们将dict子类放在本地dict类型之上。

Python的拥护者总是倾向于defaultdict尽可能使用dict.setdefault。甚至是This technique is simpler and faster than an equivalent technique using dict.setdefault():

doc引语

以类似的方式,由于字典不维护顺序,因此使用OrderedDict而不是使用dict然后对项目进行排序是可取的,以备替代使用。

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

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

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

2 个答案:

答案 0 :(得分:55)

没有一个答案,也没有一个真实而且只有一个字。在许多变量中,它取决于:

  1. 数据集的大小;
  2. 唯一键的数量与数据映射集中重复键的数量;
  3. defaultdict基础工厂的速度;
  4. OrderDict的速度与后来的一些订购步骤;
  5. Python版本。
  6. 厌恶来概括,但这里有一些普遍性:

    1. 声明This technique is simpler and faster than an equivalent technique using dict.setdefault()只是错误的。这取决于数据;
    2. 使用小数据集时,
    3. setdefault更快更简单;
    4. 对于具有更多同质密钥集的更大数据集,
    5. defaultdict更快(即,添加元素后dict的缩短程度);
    6. setdefault具有更多异构密钥集的优势;
    7. 这些结果与Python 3和Python 2不同;
    8. 除了依赖于顺序的算法以外,
    9. OrderedDict在所有情况下都比较慢,并且顺序不易重构或排序;
    10. 对于大多数dict操作,Python 3通常更快;
    11. Python 3.6的dict现在按插入顺序排序(减少了OrderedDict的用处)。
    12. 唯一的事实:它取决于!这三种技术都很有用。

      以下是一些时序代码:

      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)
      

答案 1 :(得分:7)

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

  

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

  

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