【问题标题】:List comprehension without [ ] in PythonPython中不带[]的列表推导
【发布时间】:2012-02-22 01:22:18
【问题描述】:

加入列表:

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

join 必须采用可迭代对象。

显然,join 的参数是[ str(_) for _ in xrange(10) ],它是一个list comprehension

看看这个:

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

现在,join 的参数只是str(_) for _ in xrange(10),没有[],但结果是一样的。

为什么? str(_) for _ in xrange(10) 是否也生成列表或可迭代对象?

【问题讨论】:

  • 我认为join 很可能是用 C 编写的,因此运行速度比列表理解要快得多...测试时间!
  • 显然,我完全错误地阅读了您的问题。它似乎正在为我返回一个生成器......
  • 请注意:_ 没有特殊含义,它是一个常规变量名。它通常用作一次性名称,但事实并非如此(您正在使用变量)。我会避免在代码中使用它(至少以这种方式)。

标签: python list-comprehension


【解决方案1】:

其他受访者正确回答您发现了 generator expression(其符号类似于列表推导式,但没有方括号)。

一般来说,基因表达式(因为它们被亲切地称为)比列表推导更节省内存和更快。

然而,在''.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

比较 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

【讨论】:

  • @lazyr 你的第二个时机做的工作太多了。不要将 genexp 包裹在 listcomp 周围——只需直接使用 genexp。难怪你有奇怪的时机。
  • 你能解释一下为什么 ''.join() 需要 2 次遍历迭代器来构建字符串吗?
  • @ovgolovin 我猜第一遍是对字符串的长度求和,以便能够为连接的字符串分配正确的内存量,而第二遍是复制将单个字符串放入分配的空间。
  • @lazyr 这个猜测是正确的。这正是 str.join 所做的:-)
  • 有时我真的很怀念在 SO 上“收藏”特定答案的能力。
【解决方案2】:
>>>''.join( str(_) for _ in xrange(10) )

这称为生成器表达式,在PEP 289 中有解释。

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

请注意,表达式还有第三种写法:

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

【讨论】:

  • 据我所知,可以通过类似元组的表达式生成生成器,例如( str(_) for _ in xrange(10) )。但是我很困惑,为什么() 可以在join 中省略,也就是说,代码应该是'''.join( (str(_) for _ in xrange(10)) ),对吧?
  • @Alcott 我对元组的理解是,它们实际上是由逗号分隔的表达式列表定义的,而不是括号;括号仅用于对赋值中的值进行可视化分组,或者在元组进入其他逗号分隔列表(如函数调用)时实际对值进行分组。这通常通过运行tup = 1, 2, 3; print(tup) 之类的代码来证明。考虑到这一点,使用for 作为表达式的一部分会创建生成器,而括号只是用来将其与错误编写的循环区分开来。
【解决方案3】:

您的第二个示例使用生成器表达式而不是列表推导式。不同之处在于,通过列表推导,一个列表被完全构建并传递给.join()。使用生成器表达式,项目会一一生成并由.join() 使用。后者使用的内存更少,通常速度更快。

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

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

只是“语法糖”:

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

换句话说,列表推导式只是一个生成器表达式,它被转换为一个列表。

【讨论】:

  • 你确定它们在底层是等价的吗? Timeit 说:[str(x) for x in xrange(1000)]: 262 微秒,list(str(x) for x in xrange(1000)): 304 微秒。
  • @lazyr 你是对的。列表理解更快。这就是 Python 2.x 中列表推导泄漏的原因。 GVR 写道:“”这是列表推导的原始实现的产物;多年来,它一直是 Python 的“肮脏小秘密”之一。它一开始是为了让列表理解变得非常快而故意妥协,虽然这对初学者来说不是一个常见的陷阱,但它确实偶尔会刺痛人们。”python-history.blogspot.com/2010/06/…
  • @ovgolovin listcomp 更快的原因是因为 join 必须先创建一个列表才能开始工作。您提到的“泄漏”不是速度问题——它只是意味着循环感应变量暴露在 listcomp 之外。
  • @RaymondHettinger 那么这些词是什么意思“它开始是一种故意妥协以使列表理解变得非常快”?据我了解,它们的泄漏与速度问题有关。 GVR 还写道:“对于生成器表达式,我们无法做到这一点。生成器表达式是使用生成器实现的,其执行需要单独的执行框架。因此,生成器表达式(特别是如果它们在短序列上迭代时) 效率低于列表推导式。”
  • @ovgolovin 您从 listcomp 实现细节到 str.join 执行方式的原因是错误的。 str.join 代码中的第一行之一是seq = PySequence_Fast(orig, "");,这是迭代器在调用 str.join() 时比列表或元组运行得更慢的唯一原因。如果您想进一步讨论,欢迎您开始聊天(我是 PEP 289 的作者,LIST_APPEND 操作码的创建者,以及优化 list() 构造函数的人,所以我确实有一些熟悉问题)。
【解决方案4】:

如前所述,它是generator expression

来自文档:

只有一个参数的调用可以省略括号。详见Calls部分。

【讨论】:

    【解决方案5】:

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

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

    连接之后的部分,( str(_) for _ in xrange(10) ) 本身就是一个生成器表达式。你可以这样做:

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

    它的意思和你在上面第二种情况下写的完全一样。

    生成器有一些非常有趣的特性,其中最重要的一点是它们不会在您不需要时分配整个列表。相反,像 join 这样的函数一次将一个项目从生成器表达式中“抽出”,在微小的中间部分上完成它的工作。

    在您的特定示例中,列表和生成器的性能可能并没有太大的不同,但总的来说,我更喜欢尽可能使用生成器表达式(甚至生成器函数),主要是因为生成器非常罕见 比完整列表实现慢

    【讨论】:

      【解决方案6】:

      这是一个生成器,而不是列表推导式。生成器也是可迭代的,但不是先创建整个列表然后将其传递给 join,而是将 xrange 中的每个值一个一个传递,这样效率会高得多。

      【讨论】:

        【解决方案7】:

        第二个join 调用的参数是一个生成器表达式。它确实产生了一个可迭代对象。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-05-04
          • 2016-02-13
          • 1970-01-01
          • 2021-04-12
          • 2010-11-07
          • 1970-01-01
          • 2010-10-28
          • 2016-04-07
          相关资源
          最近更新 更多