【问题标题】:List comprehension in python, how to?python中的列表理解,如何?
【发布时间】:2013-09-30 22:10:27
【问题描述】:

我正在阅读有关 Python 的内容,我想解决列表推导问题。问题很简单:

编写一个程序,在某个 n 之前给出 3 和 5 的倍数之和

取 n = 1000(欧拉项目,第一个问题)

我想做这样的事情:

[mysum = mysum + i for i in range(2,1000) if i%3==0 or i%5==0]

只有一行...但这不起作用。

  1. 这可以通过列表推导来实现吗?如何??
  2. 另外,什么时候最好使用列表推导式?

【问题讨论】:

  • 您的问题描述说您正在过滤 3 和 5 的倍数,但您的代码正在过滤 2 和 5 的倍数。
  • 是的,我修好了。谢谢
  • @Edwardo:作为对 cme​​ts 中使用的术语的说明,您一直将列表理解与“函数式编程”进行对比。列表推导是从 Haskell 引入 Python 的。使用迭代器作为惰性列表的算法也直接来自 Haskell 和相关语言。使用生成器表达式链来表达计算阶段仍然是一种函数式风格,或者可能是函数式声明式;您使用裸表达式而不是声明的函数这一事实并没有真正降低它的功能性;只是少了 Lisp-y。

标签: python list-comprehension


【解决方案1】:

列表推导的要点是生成一个结果值列表,每个源值一个(或者每个匹配源值一个,如果您有if 子句)。

换句话说,它与map (或mapfilter 调用链,如果您有多个子句)相同,除了您可以将每个新值描述为表达式旧值,而不必将其包装在函数中。

您不能将语句(如mysum = mysum + i)放入理解中,只能放入表达式。而且,即使你能想出一个具有你想要的副作用的表达式,这仍然是对理解的令人困惑的误用。如果您不想要结果值列表,请不要使用列表推导式。

如果您只是想在循环中执行计算,请编写显式 for 循环。


如果你真的需要它是一行,你总是可以这样做:

for i in [i for i in range(2, 10) if i%2==0 or i%5==0]: mysum += i

用理解构建要循环的事物列表;在for 循环中进行副作用-y 计算。

(当然,这是假设您已经在 mysum 中获得了一些价值来添加,例如,使用 mysum = 0。)

而且,一般来说,只要您想要一个仅用于循环一次的推导式,您想要的推导式就是生成器表达式,而不是列表推导式。所以,把这些方括号变成括号,你会得到:

for i in (i for i in range(2, 10) if i%2==0 or i%5==0): mysum += i

不过,无论哪种方式,两行代码都更具可读性和 Python 风格:

for i in (i for i in range(2, 10) if i%2==0 or i%5==0):
    mysum += i

……甚至三个:

not2or5 = (i for i in range(2, 10) if i%2==0 or i%5==0) 
for i in not2or5:
    mysum += i

如果您使用的语言使 reduce/fold 比循环更直观,那么 Python 有一个 reduce 函数。然而,仅仅使用它来消除 for 循环并将块语句变成单行语句通常不被认为是 Pythonic。

更一般地说,在 Python 中,试图将内容塞进一行通常会降低可读性,而不是更多,这通常意味着您最终会在脑海中输入更多字符和处理更多标记,这不仅仅是取消节省线路的任何收益。


当然,在这种特定情况下,您真正​​想要做的只是总结一个列表的值。这正是sum 所做的。这很容易理解。所以:

mysum += sum(i for i in range(2, 10) if i%2==0 or i%5==0)

(同样,这是假设您已经在 mysum 中有一些要添加的内容。如果没有,只需将 += 更改为 =。后面的所有示例都是如此,所以我就不解释了。)


话虽如此,我可能会将其写为显式嵌套块:

for i in range(2, 10):
    if i%2==0 or i%5==0:
        mysum += i

…或者作为一系列迭代器转换(在这种情况下实际上只是一个转换):

not2or5 = (i for i in range(2, 10) if i%2==0 or i%5==0)
mysum += sum(not2to5)

以这种方式拆分内容确实没有任何成本(只要您使用生成器表达式而不是列表推导式),而且它通常会使您的代码意图更加明显。


对生成器表达式的一些进一步解释:

生成器表达式就像一个列表推导式,除了它构建一个迭代器而不是一个列表。迭代器类似于某些函数式语言中的“惰性列表”,只是您只能使用一次。 (通常,这不是问题。在上面的所有示例中,我们唯一想做的就是将它传递给sum 函数或在for 循环中使用它,然后我们再也不会引用它。)当您对其进行迭代时,每个值都是按需构建的,然后在您进入下一个之前被释放。

这意味着空间复杂度是恒定的,而不是线性的。您一次只能在内存中获得一个值,而对于列表,您显然已经获得了所有值。这通常是一个巨大的胜利。

但是,时间复杂度没有改变。列表推导会预先完成所有工作,因此它是构建的线性时间,然后可以免费使用。生成器表达式在您对其进行迭代时完成工作,因此可以免费构建,然后线性使用。无论哪种方式,同一时间。 (由于缓存/内存局部性、流水线等原因,生成器表达式实际上可以明显更快,更不用说避免所有内存移动和分配成本。另一方面,对于琐碎的情况,它会更慢,至少在CPython,因为它必须通过完整的迭代器协议而不是列表的快速特殊情况。)

(我在这里假设每个步骤的工作是恒定的——显然[sum(range(i)) for i in range(n)] 在 n 中是二次的,而不是线性的......)

【讨论】:

  • 阿巴内特,非常感谢!但我认为你在前两个例子中有一个错字。我认为必须首先声明 mysum。与 las 示例相同。在第三个示例中(看起来不错)必须是 '=' 而不是 '+=' 。你说的一代表达是什么意思?它有相同的大O?我只想用 Python 编写好的代码 :) 我知道 map 和 reduce 是如何工作的(至少)。我用 Racket(Scheme 的一种方言)写了一些代码。我将使用这个函数式编程工具发布我的两行代码。请说出您的想法,以及就大 O 分析而言,哪种方法是最好的方法。
  • @Edwardo:您的原始代码没有为mysum 指定初始值,它只是将内容添加到那里。所以,我写了我的代码来做同样的事情。如果这不是你想要的,我会修改我的答案。
  • @Edwardo:对于你问题的后半部分......在评论中解释起来太难了,所以我将它添加到我的答案中。
  • 复杂度如何(big-O)?
  • 生成器的另一个优点是能够短路测试,例如 allany - 将百万项列表传递给 all 之前必须先构建一个包含 1,000,000 个元素的列表开始看价值观。传递给 all 的 1,000,000 元素生成器将在第一个 False 值处停止。
【解决方案2】:

你快到了!试试这个:

mysum = sum([i for i in range(2,10) if i%2==0 or i%5==0])

这将从“循环”中创建一个列表,然后将此列表传递给sum 函数。

mylist = [*some expression using i* for i in iterable] 这样的列表理解是

mylist = []
for i in iterable:
    mylist.append(*some expression using i*)

mylist = [*some expression using i* for i in iterable if *boolean with i*] 这样的列表理解是

mylist = []
for i in iterable:
    if *boolean with i*:
        mylist.append(*some expression using i*)

您可以在需要使用某些表达式构造 new 列表时使用它们。列表解析实际上通常比等效的for 循环更有效,因为它们在底层执行C 中的代码,而不是通过解释的python。

【讨论】:

  • 哇,蟒蛇的力量!谢谢!! @SethMMorton
  • 您不能将 statement 放入 listcomp(或作为参数作为 append 调用),只能放入 expression。这是 Python 中一个非常重要的区别。
  • 谢谢! boolean 和 i 变量中的 * 是什么意思??
  • @abarnert 是的。接得好。编辑。我在写声明的时候有一种恶心的感觉,我想不出合适的词。
  • @Edwardo 这是我说“在此处插入适当的代码”的方式,在 python 中使用了一些语法上无效的东西,所以很明显应该在哪里替换你的命令。
【解决方案3】:

这是我使用过滤器、求和和减少的 2 行实现:

def f(x): return x%3 == 0 or x%5 == 0
print sum(filter(f,range(2,1000)))

不错吧?你能解释一下这段代码吗:

not2or5 = (i for i in range(2, 1000) if i%3==0 or i%5==0)
print sum(not2or5)     

【讨论】:

  • 您正在创建一个生成器(() 而不是[])。它不是创建循环,而是创建一个对象,该对象仅在某些调用函数请求时才返回值。
  • mapfilter 的任何调用都等效于列表推导(或者,在Python 3.x 中,生成器表达式),除了您必须将表达式或谓词包装在功能。我试图在回答中解释这一点;如果仍然不清楚,您应该阅读有关该主题的教程。官方教程中有一个关于List Comprehensions 的部分和一个关于Iterators 的部分可能会帮助您入门,但可能会有更适合新手的解释。
  • 我有几个单行: print sum([x for x in xrange(1,1000) if x%3==0 or x%5==0]) 或者这个:print sum([[0,x][x%3==0 or x%5==0] for x in xrange(1,10**3)])
猜你喜欢
  • 1970-01-01
  • 2020-12-03
  • 1970-01-01
  • 1970-01-01
  • 2014-08-26
  • 1970-01-01
  • 2017-02-13
  • 1970-01-01
  • 2017-02-17
相关资源
最近更新 更多