GenExp与ListComp上的元组()

时间:2014-04-25 15:50:22

标签: python performance list tuples

我有一些(少数)项目的列表,例如:

my_list = [1,2,3,4,5,6,7,8,9,10]

我有一个索引元组,例如:

indexes = (1,5,9)

我想要列表中值的元组,例如:

tuple(my_list[x] for x in indexes)

但事实证明这很慢(多次运行时)。

我运行的每个列表的索引元组都没有变化 - 那么有更快的方法吗?

我使用的是Python 2.5,到目前为止我得到了令人惊讶的结果:

python -m timeit -s "indexes = (1,5,9); l = [1,2,3,4,5,6,7,8,9,10]" "tuple(l[i] for i in indexes)"
100000 loops, best of 3: 3.02 usec per loop

python -m timeit -s "indexes = (1,5,9); l = [1,2,3,4,5,6,7,8,9,10]" "tuple([l[i] for i  in indexes])"
1000000 loops, best of 3: 0.707 usec per loop

这是一个异常,还是列表理解真的比生成器表达式好得多?

3 个答案:

答案 0 :(得分:7)

operator.itemgetter(你真的必须使用2.5吗?它已经死了并埋葬了。)

除了更简单之外,由于在C中实现,它也应该稍快一些。当你知道你想要哪些索引时,你可以构造一个itemgetter项,然后在许多列表上重复调用它。它仍然需要复制N个项目并每次创建一个元组,但它应该尽可能快地执行此操作。

答案 1 :(得分:4)

元组是一个不可变的序列,所以当它被创建(及其内存分配)时,它确实需要知道它将首先包含多少元素。这意味着当从生成器表达式创建元组时,必须首先完全迭代生成器 - 因为生成器只能被消耗一次 - 并且元素需要存储在某处。所以发生的事情可以与之相比:

tuple(list(generator))

现在,从生成器表达式创建列表比使用列表推导创建列表要慢,因此您可以通过使用列表推导创建列表来节省时间。

如果你没有真正的理由使用元组,即如果你不需要不变性,你也可以保留列表而不是将其转换为元组以节省更多时间。

最后,不,没有比迭代索引和查询每个索引更好的方法。即使索引始终相同,它们仍然必须针对每个列表进行评估,因此无论如何都必须重复它。

如果实际修复了这些索引,您可以节省更多时间。因为简单的(l[1], l[5], l[9])比其他任何东西都要快得多;)


以下是来源的一些参考资料(这里使用3.4,但在2.x中应该类似):

使用内置tuple()函数创建元组是在函数PySequence_Tuple中完成的。

如果参数是一个列表,那么Python将通过调用PyList_AsTuple来明确地处理它,它基本上分配了列表长度的元组,然后只复制所有项目。

否则,它将从参数创建一个迭代器,并首先尝试猜测的长度。由于生成器没有长度,Python将使用默认猜测10并分配该长度的元组 - 注意,对于元组,我们已经分配了7个空格太多。然后它将迭代迭代器并将每个值分配给它在元组中的位置。之后,它将resize the created tuple

现在,实际差异很可能是列表推导的工作方式。列表推导本质上是一系列低级列表追加。因此,与上面描述的PySequence_Tuple中填充元组的方式类似。因此,两种方法都是平等的。然而,与生成器表达式的不同之处在于它们实际上创建了一个需要迭代的生成器(一系列产生)的开销。所以这一切都是你在列表理解时避免的额外内容。

答案 2 :(得分:0)

另一种选择,虽然比delnan慢,但是在__getitem__的cinjunction中使用map。但是,即使使用import语句,delnan的版本也会更快。

In [36]: %timeit tuple(map(my_list.__getitem__,indexes))
1000000 loops, best of 3: 653 ns per loop


In [38]: %timeit itemgetter(*indexes)(my_list)
1000000 loops, best of 3: 292 ns per loop

没有ipython:

python -m timeit -s "indexes = (1,5,9); l = [1,2,3,4,5,6,7,8,9,10]" "tuple(map(l.__getitem__,indexes))"
1000000 loops, best of 3: 0.645 usec per loop

python -m timeit -s "import operator" "indexes = (1,5,9); l = [1,2,3,4,5,6,7,8,9,10]" "operator.itemgetter(*indexes)(l)"
1000000 loops, best of 3: 0.463 usec per loop

看起来转换为元组会使map-variant比itemgetter-variant慢:

python -m timeit -s "indexes = (1,5,9); l = [1,2,3,4,5,6,7,8,9,10]" "map(l.__getitem__,indexes)"
1000000 loops, best of 3: 0.489 usec per loop