itertools.islice与列表切片的比较

14 投票
2 回答
8145 浏览
提问于 2025-04-15 22:09

我一直在尝试用一种算法把一个Python列表缩减成一个更小的列表,依据某些标准来进行筛选。因为原始列表的元素数量很大,大约有10万个,所以我想用itertools来避免多次分配内存,于是我写了这个:

reducedVec = [ 'F' if sum( 1 for x in islice(vec, i, i+ratio) if x == 'F' ) 
                         > ratio / 3.0 else 'T'
                for i in xrange(0, len(vec), ratio) ]

但是,当这个列表有大约10万个元素时,执行这个代码的时间让我很担心,居然要几分钟。然后我试着用:

reducedVec = [ 'F' if sum( 1 for x in vec[i:i+ratio] if x == 'F' ) 
                         > ratio / 3.0 else 'T'
                for i in xrange(0, len(vec), ratio) ]

本质上就是把islice换成了切片,这样执行的速度就快得多,几乎是瞬间完成。

你能想出一个合理的解释吗?我本以为这样避免反复分配一个包含大量元素的新列表,应该能节省一些计算时间,而不是让整个执行变得这么慢。

2 个回答

1

我猜测,使用 islice() 的时候,每处理 vec 中的一个元素,就会调用一次 Python 的函数。而使用扩展切片的写法,解析器能直接理解,并且可以直接转化为 CPython 的调用。

14

islice 可以处理各种可迭代的对象。为了做到这一点,它不会直接跳到第 n 个元素,而是先遍历前面 n-1 个元素,把它们丢掉,然后再返回你想要的那些元素。

你可以查看 itertools 文档 中的纯 Python 实现:

def islice(iterable, *args):
    # islice('ABCDEFG', 2) --> A B
    # islice('ABCDEFG', 2, 4) --> C D
    # islice('ABCDEFG', 2, None) --> C D E F G
    # islice('ABCDEFG', 0, None, 2) --> A C E G
    s = slice(*args)
    it = iter(xrange(s.start or 0, s.stop or sys.maxint, s.step or 1))
    nexti = next(it)
    for i, element in enumerate(iterable):
        if i == nexti:
            yield element
            nexti = next(it)

说到 itertools 文档,如果我想做这个操作,我可能会使用 grouper 的方法。虽然这样做不会真的节省内存,但如果你把它改写得更懒惰一些,可能会有帮助,这并不难。

from __future__ import division

from itertools import izip_longest
def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

reducedVec = []
for chunk in grouper(ratio, vec):
    if sum(1 for x in chunk if x == 'F') > ratio / 3:
        reducedVec.append('F')
    else:
        reducedVec.append('T')

我喜欢用 grouper 来简化连续的切片操作,觉得这个代码比原来的更容易理解。

撰写回答