List comprehension without [ ] in Python
加入列表:
1 2
| >>> ''.join([ str(_) for _ in xrange(10) ])
'0123456789' |
必须把安join迭代变量。
apparently [ str(_) for _ in xrange(10) ]join’s,和它的参数是一个列表,阅读能力。
看看这个:
1 2
| >>>''.join( str(_) for _ in xrange(10) )
'0123456789' |
现在的问题是,join’s Just str(_) for _ in xrange(10),[],但结果是相同的。
为什么?str(_) for _ in xrange(10)也产生不安或迭代变量的列表?
- 我可以想象,join很可能是用C语言编写的,因此比列表理解要快得多…测试时间!
- 显然,我把你的问题读错了。它好像在给我送回发电机…
- 请注意:_没有特殊含义,它是一个常规变量名。它通常被用作一个丢弃的名称,但事实并非如此(您使用的是变量)。我将避免在代码中使用它(至少是这样)。
其他受访者的回答是正确的,你发现了一个生成器表达式(它有一个类似于列表理解的符号,但没有周围的方括号)。
一般来说,genepxps(它们被亲切地称为genepxps)比列表理解更高效、更快。
然而,在''.join()的情况下,列表理解既更快,也更节省内存。原因是join需要对数据进行两次传递,所以它实际上需要一个真正的列表。如果你给它一个,它可以立即开始工作。如果您给它一个genexp,它将无法开始工作,直到它通过运行genexp来耗尽内存中的新列表:
1 2 3 4
| ~ $ python -m timeit '"".join(str(n) for n in xrange(1000))'
1000 loops, best of 3: 335 usec per loop
~ $ python -m timeit '"".join([str(n) for n in xrange(1000)])'
1000 loops, best of 3: 288 usec per loop |
。
将itertools.imap与map进行比较时,也会得到相同的结果:
1 2 3 4
| ~ $ python -m timeit -s'from itertools import imap' '"".join(imap(str, xrange(1000)))'
1000 loops, best of 3: 220 usec per loop
~ $ python -m timeit '"".join(map(str, xrange(1000)))'
1000 loops, best of 3: 212 usec per loop |
。
- 奇怪的是,我得到的结果与"".join(l)相反。我使用timeit设置将l设置为[str(n) for n in xrange(1000)]或(x for x in [str(n) for n in xrange(1000)])。结果:分别为21.7和1.55。iter([str(n) for n in xrange(1000)])速度更快:0.87 usec。
- @拉兹尔,你的第二个时机是做太多的工作。不要将genexp包装在listcomp中——直接使用genexp。难怪你有奇怪的时间安排。
- 为了只计算join的时间,我将除"".join(l)以外的所有内容都放入了SETUP语句中。问题就在这里——genexp在第一个循环中耗尽了,所以接下来的999999次它加入了一个空的genexp。难怪速度太快了。
- 你能解释一下为什么''.join()需要2次遍历迭代器来构建字符串吗?
- @Ovgolovin我想第一个步骤是对字符串的长度求和,以便为连接的字符串分配正确的内存量,而第二个步骤是将单个字符串复制到分配的空间中。
- @拉齐尔的猜测是正确的。str.join就是这样做的:-)
- python 3.3在这些方面看起来明显慢了很多(比如慢了40-50%)。
- @任何地方的andyhayden unicode都不是免费的。
- 有时候我真的很怀念"喜欢"某个具体答案的能力。
- 你说列表理解在这里更有效地记忆是什么意思?genexp只有O(1)空间开销,不是吗?几乎不值得一提。还是我忽略了别的东西?
- @stefanpochmann fwiw,python 3中的内存差异不同,因为python3 list comp创建了一个新的作用域,而在python 2中,list comp在周围的作用域中运行。
1
| >>>''.join( str(_) for _ in xrange(10) ) |
号
这被称为生成器表达式,并在PEP289中解释。
生成器表达式和列表理解之间的主要区别在于前者不会在内存中创建列表。
请注意,有第三种方法可以编写表达式:
1
| ''.join(map(str, xrange(10))) |
- 如我所知,生成器可以通过类似于元组的表达式生成,如( str(_) for _ in xrange(10) )。但我很困惑,为什么在join中可以省略(),这意味着代码应该类似于`.join((str(u)for u in xrange(10))),对吗?
- 我读了PEP289,现在我知道了发电机,谢谢。
第二个示例使用生成器表达式,而不是列表理解。不同的是,通过清单理解,清单被完全构建并传递给.join()。使用生成器表达式,项目逐个生成,并由.join()使用。后者使用更少的内存,通常更快。
碰巧的是,列表构造函数将很乐意使用任何iterable,包括生成器表达式。所以:
1
| [str(n) for n in xrange(10)] |
。
只是"句法糖"的意思:
1
| list(str(n) for n in xrange(10)) |
换句话说,列表理解只是一个生成器表达式,它被转换成一个列表。
- 你确定它们在引擎盖下是一样的吗?时间显示:[str(x) for x in xrange(1000)]:262,list(str(x) for x in xrange(1000)):304。
- @你说得对。列表理解更快。这就是为什么列表理解会在python 2.x中泄漏的原因。这就是gvr所写的:"这是最初实现列表理解的产物;它是多年来python的"肮脏的小秘密"之一。一开始它是一种故意的妥协,让列表理解速度极快,虽然它不是初学者的常见陷阱,但它确实偶尔会让人感到刺痛。"python history.blogspot.com/2010/06/…
- @ovgolovin listcomp更快的原因是join必须在开始工作之前创建一个列表。您提到的"泄漏"不是速度问题——它只是意味着循环感应变量暴露在listcomp之外。
- @Raymondhettinger那么,这些词的意思是"它开始是一种故意的妥协,让列表理解得如此之快"?据我所知,它们的泄漏与速度问题有关。GVR还写道:"对于生成器表达式,我们不能这样做。生成器表达式是使用生成器实现的,生成器的执行需要单独的执行框架。因此,生成器表达式(尤其是在短序列上迭代的表达式)的效率低于列表理解。"
- 尽管如此,添加这些gvr的话还是很重要的:"在开始担心清单理解在python 3中变慢之前:由于python3中进行了大量的实现工作以加快速度,python3中的清单理解和生成器表达式实际上比python 2中的要快!(两者之间不再存在速度差。)
- @ovgolovin从ListComp实现细节到str.join执行方式的原因,您做了一个错误的跳跃。str.join代码的第一行之一是seq = PySequence_Fast(orig,"");,这是迭代器在调用str.join()时比列表或元组运行得慢的唯一原因。如果您想进一步讨论,欢迎您开始聊天(我是pep 289的作者,list_append操作码的创建者,以及优化list()构造函数的人,因此我对这个问题有一些了解)。
- 顺便说一句,我并不是想暗示它们在引擎盖下是等价的——当然这是一个特定于实现的细节——只是列表理解基本上是生成器表达式的一个特例,有点甜。
- @Raymondhettinger我在第二次评论中提到,对于list(*generator expression*),列表理解仅仅是一种"句法糖分"。我所知道的一切都来自我在评论中提到的那篇文章。因此,我对这个问题的了解是相当肤浅的,几乎包含了在python 2.x中列出理解比生成器表达式更快的格言。我也不知道''.join()在构造字符串之前需要对象的长度,在这种情况下,这是减速的主要原因。
- @Raymondhettinger和这里我写评论更多是出于好奇。希望它不仅对我有意思!至少在清单理解方面,''.join()比生成器表达式更好,这并不明显。
- @如果你不介意的话,Raymondhettinger还有一个问题。据我所知,PySequence_Fast(...)与tuple(...)或list(...)几乎相同(取决于作为参数提供的对象)。因此,如果我们调用''.join(*generator expression*),gen.expression将转换为tuple。所以它几乎和''.join(tuple(*generator expression*))一样。但''.join(*list comprehension*)与''.join(list(*generator expression*))几乎相同。前两种方法的唯一区别是前者的实现方式稍有不同,因此工作速度更快并且存在泄漏。
- @Raymondhettinger使''.join(*generator expression*)执行的操作几乎与''.join(*list comprehension*)执行的操作相同,唯一的区别是*list comprehension*在作为参数发送到join之前创建了一个列表,*generator expression*在join接收到它之后才用于创建元组,后者在i上调用PySequence_Fast。我的想法正确吗?
- @Ovgolovin pysequence快(x)大致相当于x if isinstance(x, (list, tuple)) else list(x)。换句话说,如果参数已经是一个列表或元组,那么它根本不需要做任何工作。这与list(x)不同,list(x)总是列一个新的清单。
如前所述,它是一个生成器表达式。
从文档中:
The parentheses can be omitted on calls with only one argument. See section Calls for the detail.
号
如果它在parens中,而不是括号中,从技术上讲它是一个生成器表达式。在python 2.4中首次引入了生成器表达式。
http://wiki.python.org/moin/generators网站
连接后的部分,( str(_) for _ in xrange(10) )本身就是一个生成器表达式。你可以这样做:
1 2
| mylist = (str(_) for _ in xrange(10))
''.join(mylist) |
它的意思和你在上面第二个案例中写的完全一样。
生成器有一些非常有趣的属性,最不重要的是,当您不需要时,它们最终不会分配整个列表。相反,一个像join"pumps"这样的函数一次从生成器表达式中抽取一个项目,对微小的中间部分进行处理。
在您的特定示例中,list和generator的执行方式可能没有太大的不同,但一般来说,我更喜欢在任何时候使用generator表达式(甚至是generator函数),这主要是因为对于一个generator来说,要比完整的列表物化慢一些是非常罕见的。
第二个join调用的参数是一个生成器表达式。它确实产生了一个不可测的。
这是一个生成器,而不是列表理解。生成器也是iterables,但它不会先创建整个列表,然后将其传递给join,而是逐个传递xrange中的每个值,这样效率会更高。