我正在尝试根据另一个索引列表迭代列表中的元素子集。
最短/最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及其相对效率/低效率,从而了解这些方法之间的实际计算成本差异?我知道,我知道两种方法都可以正常工作,但是一旦我开始怀疑这种差异,就会感到好奇。
答案 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
的理解一样),但是它将:
tuple
而不是list
(内存效率略高,内存局部性更好,但通常没有明显的区别)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()
您需要进行分析以确定:
itemgetter
和listcomp这样的解决方案使用更多的内存,但运行速度可能更快;惰性解决方案的内存开销固定且较小,但运行速度可能会更慢)