指数效率子集

时间:2019-02-08 02:05:51

标签: python performance iteration

我正在尝试根据另一个索引列表迭代列表中的元素子集。

最短/最pythonic的方式似乎将使用列表理解来达到以下效果:

    for elt in [lst[idx] for idx in idxs]:
        elt.do_stuff()
        elt.do_more_stuff()

但是我无法撼动这种感觉,如果我要循环很多次,每次都重新构建列表理解可能会很昂贵,因此我应该花费额外的时间来写:

    for idx in indxs:
        elt = lst[idx]
        elt.do_stuff()
        elt.do_more_stuff()

谁能更了解python及其相对效率/低效率,从而了解这些方法之间的实际计算成本差异?我知道,我知道两种方法都可以正常工作,但是一旦我开始怀疑这种差异,就会感到好奇。

1 个答案:

答案 0 :(得分:3)

您的第二个循环很好,但是只需使用a generator expression,就可以使您的第一个循环工作而无需构建临时的list

for elt in (lst[idx] for idx in idxs):
    elt.do_stuff()
    elt.do_more_stuff()

或(如果有很多索引,则可能会稍快一些),方法是使用map(ab?):

for elt in map(lst.__getitem__, idxs):
    elt.do_stuff()
    elt.do_more_stuff()

在两种情况下(至少在Py3上,map返回一个迭代器,而不是新的list),其效果是在请求下一个elt时懒惰地查找每个索引;甚至在循环开始之前就不急于制作list

如果要重复查找同一组索引(即idxs不变),则可能要考虑另一个选项。您可以一次创建一个operator.itemgetter,然后使用它。它会急切地运行(就像对list的理解一样),但是它将:

  1. 返回tuple而不是list(内存效率略高,内存局部性更好,但通常没有明显的区别)
  2. 推动上述tuple的构建,开始完成,直到C层,在其中list的理解,尽管使用专用字节码,仍必须在常规解释器中完成所有工作,至少在CPython上这比大多数推送到C的工作要慢

对于这种方法,您可以这样做:

# Done once up front
from operator import itemgetter
getidxs = itemgetter(*idxs)  # Note: Will fail if idxs is not at least length 2; won't return tuple when getting one item

# Done every time
for elt in getidxs(lst):
    elt.do_stuff()
    elt.do_more_stuff()

您需要进行分析以确定:

  1. 是否真的需要进行这种优化
  2. 哪种解决方案对您来说最有意义(itemgetter和listcomp这样的解决方案使用更多的内存,但运行速度可能更快;惰性解决方案的内存开销固定且较小,但运行速度可能会更慢) li>