【问题标题】:Fast iterating over first n items of an iterable (not a list) in python在python中快速迭代可迭代(不是列表)的前n个项目
【发布时间】:2011-02-11 17:30:14
【问题描述】:

我正在寻找一种 Pythonic 方法来迭代可迭代的第一个 n 项目(upd:在常见情况下不是列表,因为列表是微不足道的),它是尽可能快地做到这一点非常重要。这就是我现在的做法:

count = 0
for item in iterable:
 do_something(item)
 count += 1
 if count >= n: break

在我看来并不整洁。另一种方法是:

for item in itertools.islice(iterable, n):
    do_something(item)

这看起来不错,问题是与某些生成器一起使用是否足够快?例如:

pair_generator = lambda iterable: itertools.izip(*[iter(iterable)]*2)
for item in itertools.islice(pair_generator(iterable), n):
 so_something(item)

与第一种方法相比,它的运行速度是否足够快? 有更简单的方法吗?

【问题讨论】:

  • 回答“足够快”的唯一方法是自己进行基准测试。
  • 为什么“尽可能快地做到这一点非常重要”?你能用 pstats 结果来证明这一点吗?我怀疑您使用 islice 的解决方案实际上会证明性能方面最合理的解决方案,但当然我们不知道没有时间。

标签: iterator performance python generator


【解决方案1】:

for item in itertools.islice(iterable, n): 是最明显、最简单的方法。它适用于任意迭代,并且是 O(n),就像任何理智的解决方案一样。

可以想象另一种解决方案可能具有更好的性能;如果没有时间,我们不会知道。除非您 profile 您的代码并发现此调用是热点,否则我不建议您打扰时间。除非它被埋在一个内部循环中,否则它是否会是非常值得怀疑的。过早的优化是万恶之源。


如果我要寻找替代解决方案,我会考虑像for count, item in enumerate(iterable): if count > n: break ... 和for i in xrange(n): item = next(iterator) ... 这样的解决方案。我不认为这些会有所帮助,但如果我们真的想比较事物,它们似乎值得一试。如果我陷入我分析并发现这是一个内部循环中的热点的情况(这真的是你的情况吗?),我也会尝试通过获取@987654325 来简化名称查找全局iterools 的@ 属性已将函数绑定到本地名称。

这些是您只有在证明它们会有所帮助后才能做的事情。人们尝试在其他时间做很多。这无助于使他们的程序明显更快。这只会让他们的程序变得更糟。

【讨论】:

  • 非常感谢您的回答,对我帮助很大!
  • 嗯,uning enumerate 对我来说也不错!至于分析和查找热点,这实际上不是我的情况,我只是希望我的代码中的一些循环具有巨大的迭代次数,这就是我提出问题的原因。现在我明白了——在这个阶段尝试优化是一个错误,我必须完成代码并对其进行测试,然后才能在需要时进行优化。再次感谢您的帮助。
【解决方案2】:

itertools 在直接适用时往往是最快的解决方案。

显然,检查的唯一方法是进行基准测试——例如,保存在aaa.py

import itertools

def doit1(iterable, n, do_something=lambda x: None):
  count = 0
  for item in iterable:
   do_something(item)
   count += 1
   if count >= n: break

def doit2(iterable, n, do_something=lambda x: None):
  for item in itertools.islice(iterable, n):
      do_something(item)

pair_generator = lambda iterable: itertools.izip(*[iter(iterable)]*2)

def dd1(itrbl=range(44)): doit1(itrbl, 23)
def dd2(itrbl=range(44)): doit2(itrbl, 23)

然后看看...:

$ python -mtimeit -s'import aaa' 'aaa.dd1()'
100000 loops, best of 3: 8.82 usec per loop
$ python -mtimeit -s'import aaa' 'aaa.dd2()'
100000 loops, best of 3: 6.33 usec per loop

很明显,itertools 在这里更快——使用您自己的数据进行基准测试以进行验证。

顺便说一句,我发现timeit 在命令行中更有用,所以我一直是这样使用它的——然后它会针对您特别尝试的速度运行正确的“数量级”循环测量,是那些 10、100、1000 等等——这里,为了区分一个微秒半的差异,十万个循环大约是正确的。

【讨论】:

  • 很奇怪,看到一个简单的解决方案比一个简洁的解决方案运行得慢,这违背了我的 cplusplus 直觉。 python 确实是最酷的语言。这是对 Mike Graham 建议不要过早优化的一个很好的补充。我想一般规则是写什么是整洁的,而不是考虑运行时间。
  • @martin,就我个人而言,我认为很多关于运行时间(主要是在可伸缩性的大O方面)——但是,一般来说,最Pythonic的习惯用法是通常会是最优化的那些,因为它们通常是我们 Python 提交者最关心的(Hettinger,itertools 和 Python 其他快速部分的作者,最近在该领域非常活跃年,就像彼得斯早些年一样,但这是 Python 提交者社区中相当普遍的现象)。
【解决方案3】:

如果是列表,则可以使用切片:

list[:n]

【讨论】:

    【解决方案4】:

    您可以使用enumerate 编写与您基本相同的循环,但使用更简单的 Python 方式:

    对于 idx, val in enumerate(iterableobj): 如果 idx > n: 休息 do_something(val)

    【讨论】:

    • 这个选项已经在上面讨论过了,看起来是个不错的选项,但我认为islice 更好,因为它不需要在循环体中添加任何额外的变量,让我看起来更清晰
    【解决方案5】:

    列表?试试看

    for k in mylist[0:n]:
         # do stuff with k
    

    如果需要,也可以使用推导式

    my_new_list = [blah(k) for k in mylist[0:n]]
    

    【讨论】:

      猜你喜欢
      • 2014-04-26
      • 2021-03-07
      • 2020-06-11
      • 2014-07-19
      • 2012-02-17
      • 1970-01-01
      • 2019-07-05
      • 2014-10-06
      相关资源
      最近更新 更多