在这里看到讨论之后:Python - generate the time difference我很好奇。我最初也认为生成器比列表更快,但是当谈到sorted()我不知道。将生成器表达式发送到sorted()而不是列表有什么好处?生成器表达式最终会在排序之前被放入sorted()中的列表吗?
编辑:让我感到悲伤的是只能接受一个答案,因为我觉得很多回复都有助于澄清问题。再次感谢大家。答案 0 :(得分:42)
sorted()
做的第一件事就是将数据转换为列表。基本上,实现的第一行(在参数验证之后)是
newlist = PySequence_List(seq);
另请参阅the full source code version 2.7和version 3.1.2。
修改:正如answer by aaronasterling所指出的,变量newlist
就是新列表。如果参数已经是列表,则会复制该参数。因此,生成器表达式确实具有使用较少内存的优势。
答案 1 :(得分:17)
查看哪个更快的最简单方法是使用timeit
,它告诉我传递列表而不是生成器更快:
>>> import random
>>> randomlist = range(1000)
>>> random.shuffle(randomlist)
>>> import timeit
>>> timeit.timeit("sorted(x for x in randomlist)",setup = "from __main__ import randomlist",number = 10000)
4.944492386602178
>>> timeit.timeit("sorted([x for x in randomlist])",setup = "from __main__ import randomlist",number = 10000)
4.635165083830486
和
>>> timeit.timeit("sorted(x for x in xrange(1000,1,-1))",number = 10000)
1.411807087213674
>>> timeit.timeit("sorted([x for x in xrange(1000,1,-1)])",number = 10000)
1.0734657617099401
我认为这是因为当sorted()
将传入的值转换为列表时,对于已经是列表而不是生成器的内容,它可以更快地完成此操作。 The source code seems to confirm this(但这是通过阅读评论而不是完全理解正在发生的一切)。
答案 2 :(得分:12)
有一个巨大的好处。因为sorted不会影响传入的序列,所以它必须复制它。如果它从生成器表达式生成列表,则只生成一个列表。如果传入了列表推导,那么首先构建它,然后sorted
复制它以进行排序。
这反映在
行中newlist = PySequence_List(seq);
引用Sven Marnach's answer。从本质上讲,这将无条件地复制传递给它的任何序列。
答案 3 :(得分:11)
在不知道序列的所有元素的情况下,无法对序列进行排序,因此传递给sorted()
的任何生成器都已耗尽。
答案 4 :(得分:8)
Python使用Timsort。 Timsort需要知道前面元素的总数,以计算minrun参数。因此,正如Sven报道的那样,给定生成器时排序的第一件事就是将其转换为列表。
也就是说,可以写一个Timsort的增量版本,它更慢地消耗了生成器的值 - 你必须在开始之前修复minrun,并接受最后有一些不平衡合并的痛苦。 Timsort分两个阶段进行。第一阶段涉及遍历整个数组,识别运行并执行插入排序以在数据无序的情况下进行运行。运行查找和插入排序本质上都是递增的。第二阶段涉及合并运行的合并;那就像现在一样。
但是,我不认为这会有很多意义。也许它会使内存管理更容易,因为你不必从生成器读入一个不断增长的数组(因为我毫无根据地假设当前的实现),你可以将每次运行读入一个小缓冲区,然后只分配一个最终 - 大小缓冲一次,最后。然而,这将涉及一次在存储器中具有2N个阵列的插槽,而如果在其增长时它增加一倍,则可以用1.5N完成增长的阵列。所以,可能不是一个好主意。答案 5 :(得分:3)
我最初还以为是一个清单 理解比列表更快
你的意思比列表更快?你的意思是比明确for
更快吗?为此,我会说它取决于:列表理解更像是一个语法糖,但它在简单的循环方面非常方便。
但是当涉及到sorted()时,我没有 知道。发送是否有任何好处 生成器表达式到sorted() 而不是一个清单?
List comprehensions和Generator表达式之间的主要区别在于Generator表达式避免了一次生成整个列表的开销。相反,它们返回一个可以逐个迭代的生成器对象,因此Generator表达式更可能用于节省内存使用。
但是你必须要理解Python中的一件事:很难分辨一种方式是否比通过观察它更快(乐观)而不是另一种方式,如果你想这样做,你应该使用timeit用于基准测试(基准测试比在一台机器上运行一个时间点更复杂)。
阅读this以获取有关某些优化技术的更多信息。
答案 6 :(得分:3)
我应该添加Dave Webb的时间答案[我提出可能是匿名编辑],当您直接访问优化的生成器 时,可能快多了;大部分开销可能是代码创建自己的列表或生成器:
>>> timeit.timeit("sorted(xrange(1000, 1, -1))", number=10000)
0.34192609786987305
>>> timeit.timeit("sorted(range(1000, 1, -1))", number=10000)
0.4096639156341553
>>> timeit.timeit("sorted([el for el in xrange(1000, 1, -1)])", number=10000)
0.6886589527130127
>>> timeit.timeit("sorted(el for el in xrange(1000, 1, -1))", number=10000)
0.9492318630218506
答案 7 :(得分:1)
如果性能很重要,为什么不处理由生成器产生的数据,并将排序应用于迭代结果?当然,只有在迭代之间不存在因果条件时才可以使用(即,不需要对排序的迭代#[i]进行任何计算的排序迭代#[i]的数据)[[i + 1])。 在这种情况下我想说的是,对生成器产生的一组可能更大的结构进行排序可能会增加许多不必要的复杂性,这可能会在处理所有元素之后发生。