关于python 2.7:iteritems有什么优势?

What is the advantage of iteritems?

我使用的是带8GB内存和1.7GHz核心i5的python 2.7.5@mac os x 10.9.3。我测试了以下时间消耗。

1
2
3
4
5
6
7
8
9
d = {i:i*2 for i in xrange(10**7*3)} #WARNING: it takes time and consumes a lot of RAM

%time for k in d: k,d[k]
CPU times: user 6.22 s, sys: 10.1 ms, total: 6.23 s
Wall time: 6.23 s

%time for k,v in d.iteritems(): k, v
CPU times: user 7.67 s, sys: 27.1 ms, total: 7.7 s
Wall time: 7.69 s

似乎Iteritems比较慢。我想知道ITeritems与直接访问dict相比有什么优势。

更新:更准确的时间配置文件

1
2
3
4
5
In [23]: %timeit -n 5 for k in d: v=d[k]
5 loops, best of 3: 2.32 s per loop

In [24]: %timeit -n 5 for k,v in d.iteritems(): v
5 loops, best of 3: 2.33 s per loop


为了回答您的问题,我们首先应该挖掘一些有关如何以及何时将iteritems()添加到API的信息。

iteritems()法在python2.2中添加了该语言中的迭代器和生成器(另请参见:dict.items()和dict.iteritems()有什么区别?.事实上,PEP 234中明确提到了该方法。因此,它被作为一种懒惰的替代品引入到已经存在的items()中。

这与在python2.1中引入的file.xreadlines()file.readlines()模式相同(顺便说一下,python2.3中已经弃用)。

在python 2.3中,增加了itertools模块,引入了懒惰的对应于mapfilter等。

换言之,当时(而且现在)有一种强烈的行动懒散的趋势。其中一个原因是提高了内存效率。另一种方法是避免不必要的计算。

我找不到任何参考资料说它是为了提高查字典的速度而引入的。它只是用来替换对items()的调用,实际上不需要返回列表。注意,这包括了比简单的for循环更多的用例。

例如,在代码中:

1
function(dictionary.iteritems())

您不能像在示例中那样简单地使用for循环来替换iteritems()。您必须编写一个函数(或者使用一个genexp,即使在引入iteritems()时它们不可用,而且它们不会是干的…)。

dict中检索项目通常是这样做的,因此提供一种内置方法是有意义的,实际上,有一种方法:items()items()的问题是:

  • 这不是懒惰,也就是说,在一个大的dict上打电话要花相当长的时间。
  • 它需要大量的记忆。如果在一个很大的dict上调用一个程序,它几乎可以将该程序的内存使用量增加一倍,这个dict包含被操作的大多数对象。
  • 大多数情况下,它只迭代一次

所以,在介绍迭代器和生成器时,很明显只需要添加一个懒惰的对应项。如果您需要一个项目列表,因为您想索引它或多次迭代,请使用items(),否则您只需使用iteritems(),并避免上面提到的问题。

使用iteritems()的优点与使用items()的优点相同,而不是手动获取值:

  • 你写的代码更少,这使得它更干燥,减少了出错的机会。
  • 代码更可读。

再加上懒散的优点。

正如我已经说过的,我不能复制您的性能结果。在我的机器上,iteritems()总是比通过键进行迭代+查找更快。不管怎样,这种差异是可以忽略的,这可能是由于操作系统处理缓存和内存的方式。换句话说,你关于效率的论点并不是反对(或支持)使用一种或另一种选择的有力论据。

在平均性能相同的情况下,使用最易读、最简洁的选项:iteritems()。这个讨论类似于问"当您可以通过具有相同性能的索引进行循环时,为什么要使用foreach?"foreach的重要性不在于迭代速度更快,而在于避免编写样板代码并提高可读性。

我想指出的是,在Python体内,iteritems()实际上被移除了。这是这个版本"清理"的一部分。python3 items()方法id(大部分)相当于python2的viewitems()方法(如果我没有弄错,实际上是一个反向端口…)。

这个版本比较懒惰(因此提供了iteritems()的替代品),而且还具有进一步的功能,例如提供"set-like"操作(例如以有效的方式查找dict之间的公共项等),因此在python3中,使用items()而不是手动检索值的原因更加令人信服。


使用具有更多描述性名称的for k,v in d.iteritems()可以使循环套件中的代码更容易阅读。


与使用系统time命令相反,在带有timeit的ipython中运行会产生:

1
2
3
4
5
6
7
d = {i:i*2 for i in xrange(10**7*3)} #WARNING: it takes time and consumes a lot of RAM

timeit for k in d: k, d[k]
1 loops, best of 3: 2.46 s per loop

timeit for k, v in d.iteritems(): k, v
1 loops, best of 3: 1.92 s per loop

我在windows上运行了这个,python 2.7.6。您是否多次运行它以确认系统本身没有发生什么问题?


从技术上讲,我知道这不是问题的答案,但是评论部分不适合放这类信息。我希望这能帮助人们更好地理解正在讨论的问题的本质。

为了彻底起见,我已经对一些不同的配置进行了计时。这些都是使用重复因子为10timeit进行计时的。这是在Mac OS X 10.9.3上使用CPython 2.7.6版,带有16GB内存和2.3GHz核心i7。

原始配置

1
2
3
4
5
python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k in d: k, d[k]'
>> 10 loops, best of 3: 2.05 sec per loop

python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k, v in d.iteritems(): k, v'
>> 10 loops, best of 3: 1.74 sec per loop

巴库鲁的建议

这个建议包括传入iteritems循环,并通过访问k上的字典为第一个循环中的变量v赋值。

1
2
3
4
5
python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k in d: v = d[k]'
>> 10 loops, best of 3: 1.29 sec per loop

python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k, v in d.iteritems(): pass'
>> 10 loops, best of 3: 934 msec per loop

第一个任务没有分配

这个函数删除第一个循环中的赋值,但保持字典访问。这是不公平的比较,因为第二个循环创建了一个附加变量,并隐式地为其赋值。

1
2
python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k in d: d[k]'
>> 10 loops, best of 3: 1.27 sec per loop

有趣的是,分配对访问本身来说是微不足道的——差别仅仅是20毫秒。在每一个比较中(即使是最后的,不公平的),江户十一〔五〕都胜出。

在原始配置中,时间百分比最接近。这可能是由于大量工作正在创建一个元组(未在任何地方分配)。一旦从方程中去掉这一点,两种方法之间的差异就变得更加明显了。


dict.iter()在python 3.5中大获全胜。

下面是一个小的性能统计:

1
2
3
4
5
d = {i:i*2 for i in range(10**3)}
timeit.timeit('for k in d: k,d[k]', globals=globals())
75.92739052970501
timeit.timeit('for k, v in d.items(): k,v', globals=globals())
57.31370617801076