Python中不带[]的列表理解


85

加入清单:

>>> ''.join([ str(_) for _ in xrange(10) ])
'0123456789'

join 必须采取迭代。

显然,join的论点是[ str(_) for _ in xrange(10) ],这是一个列表理解

看这个:

>>>''.join( str(_) for _ in xrange(10) )
'0123456789'

现在,join的参数为just str(_) for _ in xrange(10),no [],但结果相同。

为什么?是否str(_) for _ in xrange(10)还会产生列表或可迭代项?


1
我想这join很可能是用C编写的,因此比列表理解要快得多……测试时间!
乔尔·科内特

显然,我读到您的问题完全错了。似乎正在为我归还发电机...
Joel Cornett

18
只是一个注释:_没有特殊含义,它是一个常规变量名。它通常被用作一次性使用的名称,但事实并非如此(您正在使用变量)。我会避免在代码中使用它(至少以这种方式)。
rplnt 2012年

Answers:


67
>>>''.join( str(_) for _ in xrange(10) )

这称为生成器表达式,并在PEP 289中进行了说明。

生成器表达式与列表理解之间的主要区别在于,前者不在内存中创建列表。

请注意,还有第三种编写表达式的方式:

''.join(map(str, xrange(10)))

1
据我所知,可以通过类似tuple的表达式来生成生成器( str(_) for _ in xrange(10) )。但是令我感到困惑的是,为什么()可以在中省略join,这意味着代码应该像`''.join((xrange(10)中_的str(_))),对吗?
奥尔科特2012年

1
@Alcott我对元组的理解是,它们实际上是由逗号分隔的表达式列表而不是括号定义的;如果元组要放入其他逗号分隔的列表(如函数调用)中,则只有在括号中才可以对赋值中的值进行可视化分组或对值进行实际分组。这通常通过运行类似的代码来演示tup = 1, 2, 3; print(tup)。考虑到这一点,将其for用作表达式的一部分将创建生成器,并且括号就在其中,以将其与错误编写的循环区分开。
Eric Ed Lohmar,

132

其他回答者的正确回答是您发现了一个生成器表达式(其表达与列表推导类似,但没有方括号)。

通常,genexps(众所周知)与列表推导相比,具有更高的存储效率和速度。

但是,在''.join()列表推导的情况下,它更快且内存使用效率更高。原因是联接需要对数据进行两次传递,因此它实际上需要一个真实的列表。如果您给它一个,它可以立即开始工作。如果改为给它一个genexp,它将无法开始工作,直到它通过运行genexp到穷竭在内存中建立一个新列表:

~ $ 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.imapmap时,结果相同:

~ $ 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

4
@lazyr您的第二个时间安排太多了。不要将genexp包裹在listcomp周围-只需直接使用genexp。难怪你有奇怪的时机。
Raymond Hettinger

11
您能解释一下为什么''.join()需要2遍遍迭代器来构建字符串吗?
ovgolovin 2012年

27
@ovgolovin我想​​第一遍是对字符串的长度求和,以便能够为连接的字符串分配正确的内存量,而第二遍是将单个字符串复制到分配的空间中。
Lauritz V. Thaulow 2012年

20
@lazyr这个猜测是正确的。这正是str.join的作用:-)
Raymond Hettinger

4
有时我真的很想念“喜欢” SO特定答案的能力。
航空

5

第二个示例使用生成器表达式而不是列表推导。区别在于通过列表理解,可以完全构建列表并将其传递给.join()。使用生成器表达式,项将被一一生成,并被消耗.join()。后者使用较少的内存,并且通常更快。

碰巧的是,列表构造函数将愉快地使用任何可迭代的函数,包括生成器表达式。所以:

[str(n) for n in xrange(10)]

只是“语法糖”的意思:

list(str(n) for n in xrange(10))

换句话说,列表理解只是变成列表的生成器表达式。


2
您确定它们在引擎盖下是等效的吗?Timeit说:[str(x) for x in xrange(1000)]:262微秒,list(str(x) for x in xrange(1000))304微秒。
Lauritz V. Thaulow 2012年

2
@lazyr你是对的。列表理解速度更快。这就是列表理解在Python 2.x中泄漏的原因。这就是GVR写道:“”这是列表理解的原始实现的产物。多年来,它一直是Python的“肮脏的小秘密”之一。它开始了作为有意的妥协,使列表理解瞬息万变,虽然它是不适合初学者的一个常见问题,它肯定刺痛了人们偶尔会“。python-history.blogspot.com/2010/06/...
ovgolovin

3
@ovgolovin listcomp更快的原因是因为join必须先创建一个列表,然后它才能开始工作。您所指的“泄漏”不是速度问题,它只是意味着循环感应变量暴露在listcomp之外。
雷蒙德·赫汀格

1
@RaymondHettinger那么,这些词是什么意思呢?“它最初是为了使列表理解快速达到盲目目的而做出的妥协”?据我了解,它们的泄漏与速度问题有关。GVR还写道:“对于生成器表达式,我们无法做到这一点。生成器表达式是使用生成器实现的,生成器的执行需要一个单独的执行框架。因此,生成器表达式(特别是如果它们在短序列上进行迭代)的效率低于列表理解。 ”
ovgolovin 2012年

4
@ovgolovin您从listcomp实现细节到str.join为何执行其行为的方式做出了错误的跳跃。str.join代码中的第一行是seq = PySequence_Fast(orig, "");,这是当调用str.join()时,迭代器比列表或元组运行更慢的唯一原因。如果您想进一步讨论,欢迎您开始聊天(我是PEP 289的作者,LIST_APPEND操作码的创建者以及优化list()构造函数的人,所以我确实有一些建议熟悉该问题)。
Raymond Hettinger 2012年


4

如果在括号内,但不在方括号中,则从技术上讲,它是一个生成器表达式。生成器表达式最早是在Python 2.4中引入的。

http://wiki.python.org/moin/Generators

连接之后的部分( str(_) for _ in xrange(10) )本身就是一个生成器表达式。您可以执行以下操作:

mylist = (str(_) for _ in xrange(10))
''.join(mylist)

它的含义与您在上述第二种情况下写的完全一样。

生成器具有一些非常有趣的属性,其中最重要的是它们在不需要一个列表时最终不会分配整个列表。取而代之的是,诸如join之类的函数一次将项从生成器表达式中“抽出”,并在微小的中间部分上完成其工作。

在您的特定示例中,列表和生成器的性能可能并没有很大不同,但是总的来说,我更愿意在可能的情况下使用生成器表达式(甚至生成器函数),主要是因为生成器的速度比完整列表慢的极少物化。


1

那是一个生成器,而不是列表理解。生成器也是可迭代的,但是与其先创建整个列表然后将其传递给联接,不如先创建整个列表,它会逐个传递xrange中的每个值,这样可以提高效率。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.