Python 使用生成器表达式而不是列表排序()

Python 使用生成器表达式而不是列表排序(),python,optimization,Python,Optimization,看到这里的讨论后:我感到好奇。我最初还认为生成器比列表快,但说到sorted()我不知道。将生成器表达式发送到sorted()而不是列表有什么好处吗?在进行排序之前,生成器表达式是否会在sorted()中生成一个列表 编辑:我只能接受一个答案,这让我很难过,因为我觉得很多回答都有助于澄清这个问题。再次感谢大家。如果不知道序列的所有元素,就无法对序列进行排序,因此传递给sorted()的任何生成器都已用尽。排序()的第一件事是将数据转换为列表。基本上,实现的第一行(在参数验证之后)是 newlis

看到这里的讨论后:我感到好奇。我最初还认为生成器比列表快,但说到sorted()我不知道。将生成器表达式发送到sorted()而不是列表有什么好处吗?在进行排序之前,生成器表达式是否会在sorted()中生成一个列表


编辑:我只能接受一个答案,这让我很难过,因为我觉得很多回答都有助于澄清这个问题。再次感谢大家。

如果不知道序列的所有元素,就无法对序列进行排序,因此传递给
sorted()
的任何生成器都已用尽。

排序()的第一件事是将数据转换为列表。基本上,实现的第一行(在参数验证之后)是

newlist = PySequence_List(seq);
另见和

Edit:如中所述,变量
newlist
是一个新列表。如果参数已经是列表,则将复制该参数。因此,生成器表达式确实具有使用更少内存的优势

我最初还认为 理解比列表快

你说比列表快是什么意思?你的意思是比显式的
更快吗?为此,我会说这取决于:列表理解更像是一种语法糖,但当涉及到简单循环时,它非常方便

但说到分类()我不知道 知道。发送邮件有什么好处吗 已排序()的生成器表达式 而不是一份清单

列表理解和生成器表达式之间的主要区别在于生成器表达式避免了一次生成整个列表的开销。相反,它们返回一个可以逐个迭代的生成器对象,因此生成器表达式更有可能用于节省内存使用

但是,您必须理解Python中的一件事:很难通过查看一种方法来判断一种方法是否比另一种方法更快(乐观),如果您想这样做,您应该使用它进行基准测试(基准测试比在一台机器上运行一次更复杂)


有关一些优化技术的更多信息,请阅读。

Python使用Timsort。Timsort需要预先知道元素的总数,以计算minrun参数。因此,正如Sven所报告的,当给定一个生成器时,排序所做的第一件事就是将其转换为一个列表

这就是说,可以编写一个增量版本的Timsort,它消耗生成器中的值的速度要慢得多——您只需在开始之前修复minrun,并接受在最后进行一些不平衡合并的痛苦。Timsort分两个阶段工作。第一个阶段涉及通过整个阵列,识别运行并进行插入排序,以在数据无序的地方进行运行。运行查找和插入排序本质上都是增量的。第二阶段涉及排序运行的合并;那将和现在一样发生


不过,我认为这没有多大意义。也许这会使内存管理更容易,因为不必从生成器读取到不断增长的数组中(我毫无根据地假设当前的实现就是这样),您可以将每次运行读取到一个小的缓冲区中,然后在最后只分配一次最终大小的缓冲区。然而,这将涉及在内存中同时有2N个数组插槽,而一个不断增长的数组如果在增长时加倍,则可以使用1.5N。所以,这可能不是个好主意。

这有很大的好处。因为排序不会影响传入的序列,所以它必须复制它。如果它从生成器表达式生成列表,则只生成一个列表。如果传入了一个列表理解,那么首先会生成该列表,然后
排序
复制该列表进行排序

这反映在线条上

newlist = PySequence_List(seq);

引用于。本质上,这将无条件地复制传递给它的任何序列。

最简单的方法是使用,它告诉我传递列表比传递生成器更快:

>>> 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
以及:


我认为这是因为当
sorted()
将传入值转换为列表时,对于已经是列表的内容,它可以比对于生成器更快地执行此操作。(但这是通过阅读评论,而不是完全理解正在发生的一切)。

如果性能很重要,为什么不在生成器生成数据时处理数据,并对迭代结果应用排序?当然,只有在迭代之间没有因果条件的情况下(即排序迭代#[i]的数据不需要为排序迭代#[i+1]进行任何计算),才可以使用该方法。
在这种情况下,我想说的是,对生成器生成的一组可能更大的结构进行排序可能会给排序增加很多不必要的复杂性,而排序可能发生在处理所有元素之后。

我应该补充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

这是有道理的。我还想知道sorted()在接收到生成器时做了什么。在执行排序之前,它是否会立即将其转换为列表,或者排序算法在生成器上的第一次传递是否会对实际排序做任何工作。查看“在线排序”,如“稳定排序”,但它会在获得元素时进行排序,即排序,而不知道序列的所有元素。非常感谢。您认为在发电机第一次运行期间进行一些工作有什么好处吗?我知道总的来说这是相对无关紧要的,但看起来可能有点小
>>> 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