在迭代切片时避免创建新列表的Pythonic方法?

时间:2015-02-05 14:01:35

标签: python python-2.x

相当常见的模式是遍历列表的一部分并搜索/选择/处理项目:

for item in array[start:end]:
   # do something

据我所知,这会创建一个新的列表,从array[start:end]复制项目,遍历它,然后临时列表被垃圾收集。这看起来像是浪费CPU使用率。

使用itertools.islice解决了这个问题。

问题:为什么这个用得不多,而且教会像教了可变默认函数参数的危险等等?

更新

正如评论中所指出的,“itertools.islice用于迭代,而不是列表。它必须在到达第n个元素之前迭代一堆”。

我猜islice不是一个很好的例子,但创建一个临时列表只是为了走过它是一个现存的问题。不是吗?

你可以这样做:

for i in xrange(start, end):
    item = array[i]
    # do something with the item

是否有 pythonic 方法迭代序列的一部分而不创建它的副本?。因为不支持此示例中的负索引,并且它不如for item in sequence那么好。

1 个答案:

答案 0 :(得分:0)

即使创建切片,切片也是Pythonic的原因是,切片的成本通常在宏伟的方案中微不足道。如果您从list中分割10,000个项目,只是为了对其进行迭代并丢弃它们,则成本为:

  1. 为10,000个指针(40K-80K字节)分配空间
  2. 将指针从源复制到新空间
  3. 增加每个指针的引用计数
  4. 在此处使用内容
  5. 减少每个指针的引用计数
  6. 释放空间

在C级别,相对于解释器本身的开销,分配几个KB的RAM和递增/递减的几千个整数是微不足道的(请记住,无论您做什么,都会发生引用计数更新当您循环并将每个元素存储在命名变量中时);使用结果start进行 几乎比创建和销毁它要昂贵得多,尤其是在切片很小的情况下(其中的解释器开销任何东西,您都可以用替换成本来代替它。

如果副本确实有问题,并且islice索引很重要(因此start无法跳至for i in xrange(start, end): item = array[i] # do something with the item 点,则直接使其不可行,并且该切片足够大,您可以合理地担心内存),我所知道的最Python化的方法基本上就是您提供的示例循环:

from future_builtins import map  # Only needed on Python 2 to get Py3 generator based map

for item in map(array.__getitem__, xrange(start, end)):  # range on Python 3
    # do something with the item

如果这太慢了(重复的索引会在CPython参考解释器中带来大量的解释器开销),那么最Python化的选择就是将索引推到C层:

list

这避免了临时的map,而换来了更高的每个元素成本(因为无法完全避免索引,只是隐藏在C层而没有字节码执行)。不过绝对没有Python风格(__getitem__常常被人们所皱眉,而对诸如Result <- merge(Employees, Departments, all.x=TRUE) Result <- Departments[Employees] # same as above 之类的特殊方法的显式引用则非常丑陋)。