在Python中没有[]的列表理解

Alc*_*ott 76 python list-comprehension

加入清单:

>>> ''.join([ str(_) for _ in xrange(10) ])
'0123456789'
Run Code Online (Sandbox Code Playgroud)

join 必须采取迭代.

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

看这个:

>>>''.join( str(_) for _ in xrange(10) )
'0123456789'
Run Code Online (Sandbox Code Playgroud)

现在,join这个论点只是str(_) for _ in xrange(10),不[],但结果是一样的.

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

Ray*_*ger 125

其他受访者回答您发现了一个生成器表达式(其表达式类似于列表推导但没有周围的方括号)是正确的.

通常,genexps(因为它们被亲切地知道)比列表推导更有效且更快.

但是,在这种情况下''.join(),列表理解更快,内存效率更高.原因是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
Run Code Online (Sandbox Code Playgroud)

比较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
Run Code Online (Sandbox Code Playgroud)

  • @ovgolovin我猜第一遍是对字符串的长度求和,以便能够为连接字符串分配正确的内存量,而第二遍是将各个字符串复制到分配的空间中. (24认同)
  • @lazyr那猜测是对的.这正是str.join所做的:-) (19认同)
  • 你能解释一下为什么`''.join()`需要2次遍历迭代器才能构建一个字符串? (10认同)
  • @lazyr你的第二个时机是做太多工作.不要将genexp包装在listcomp周围 - 只需直接使用genexp.难怪你有奇怪的时间. (4认同)
  • 有时我真的很想念"喜欢"特定答案的能力. (3认同)
  • 为了只计算 `join` 的时间,我将除了 `"".join(l)` 之外的所有内容都放在了设置语句中。问题就在这里—— genexp 在第一次循环中就耗尽了,所以接下来的 999999 次它加入了一个空的 genexp。难怪速度这么快。 (2认同)
  • Python 3.3在这些方面看起来要慢得多(比如慢40-50%) (2认同)
  • @AndyHayden Unicode-到处都没有成本. (2认同)
  • @MarkRansom:它没有同样的问题;“列表”具有摊销的“O(1)”追加(每次空间不足时,它都会分配当前容量的倍数,并且暂时不必再次重新分配)。`str` 是不可变的,这意味着即使使用就地连接优化,它也不会故意过度分配单个连接,因此*每个*连接都会触发一个 `realloc`,其中一些实际上必须移动 `str` (当堆没有空间来就地扩展分配)。重复串联是“O(n**2)”,“列表”化是“O(n)”。 (2认同)

NPE*_*NPE 60

>>>''.join( str(_) for _ in xrange(10) )
Run Code Online (Sandbox Code Playgroud)

这称为生成器表达式,在PEP 289中进行了解释.

生成器表达式和列表推导之间的主要区别在于前者不在内存中创建列表.

请注意,第三种方法是编写表达式:

''.join(map(str, xrange(10)))
Run Code Online (Sandbox Code Playgroud)

  • @Alcott我对元组的理解是它们实际上是由逗号分隔的表达式列表而不是括号定义的;括号仅用于在视觉上对赋值中的值进行分组,或者在元组要进入其他某个以逗号分隔的列表(例如函数调用)时实际对值进行分组。这通常通过运行“tup = 1, 2, 3;”之类的代码来证明。打印(tup)`。考虑到这一点,使用“for”作为表达式的一部分会创建生成器,而括号只是将其与错误编写的循环区分开来。 (2认同)

kin*_*all 5

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

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

[str(n) for n in xrange(10)]
Run Code Online (Sandbox Code Playgroud)

只是“语法糖”的意思:

list(str(n) for n in xrange(10))
Run Code Online (Sandbox Code Playgroud)

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

  • @ovgolovin listcomp更快的原因是因为* join *必须先创建一个列表,然后才能开始工作。您所指的“泄漏”不是速度问题,它只是意味着循环感应变量暴露在listcomp之外。 (3认同)
  • @ovgolovin您从listcomp实现细节到str.join为何执行其行为的方式上走得不正确。str.join代码中的第一行是``seq = PySequence_Fast(orig,“”);``,这是迭代器在调用str.join()时比列表或元组运行更慢的唯一原因。如果您想进一步讨论,欢迎您开始聊天(我是PEP 289的作者,LIST_APPEND操作码的创建者以及优化list()构造函数的人,所以我确实有一些建议熟悉该问题)。 (3认同)
  • 您确定它们在引擎盖下是等效的吗?Timeit说:`[在xrange(1000)中x的str(x)]]:262 usc,`在xrange(1000)中x的list(str(x)):304 usc。 (2认同)
  • @lazyr你是对的。列表理解速度更快。这就是列表理解在Python 2.x中泄漏的原因。这就是GVR写道:“”这是列表理解的原始实现的产物;多年来,它一直是Python的“肮脏的小秘密”之一。它起初是一种有意的折衷,目的是使列表理解迅速变得盲目,虽然对于初学者来说这不是一个常见的陷阱,但它确实偶尔会刺伤人们。” http://python-history.blogspot.com/2010/06/from- list-comprehensions-to-generator.html (2认同)

mon*_*kut 5

如前所述,它是一个生成器表达式

从文档中:

仅带有一个参数的调用可以省略括号。详细信息请参见“ 通话 ” 部分。