5

我想使用列表理解来避免编写附加到某些列表的 for 循环。但它可以与返回多个值的函数一起使用吗?我希望这个(简化示例)代码能够工作......

def calc(i):
    a = i * 2
    b = i ** 2
    return a, b

steps = [1,2,3,4,5]

ay, be = [calc(s) for s in steps]

...但它没有:(

附加到每个列表的 for 循环有效:

def calc(i):
    a = i * 2
    b = i ** 2
    return a, b

steps = [1,2,3,4,5]

ay, be = [],[]

for s in steps:
    a, b = calc(s)
    ay.append(a)
    be.append(b)

有没有更好的方法或者我只是坚持这个?

4

3 回答 3

11

zip与 一起使用*

>>> ay, by = zip(*(calc(x) for x in steps))
>>> ay
(2, 4, 6, 8, 10)
>>> by
(1, 4, 9, 16, 25)
于 2013-09-29T19:08:32.520 回答
4

返回迭代器的可怕的“节省空间”版本:

from itertools import tee

ay, by = [(r[i] for r in results) for i, results in enumerate(tee(map(calc, steps), 2))]

但基本上只是使用zip,因为大多数时候它不值得丑陋。


解释:

zip(*(calc(x) for x in steps))

将做得到(calc(x) for x in steps)一个迭代[(2, 1), (4, 4), (6, 9), (8, 16), (10, 25)]

当您打开包装时,您执行的操作相当于

zip((2, 1), (4, 4), (6, 9), (8, 16), (10, 25))

所以所有的项目都一次存储在内存中。证明:

def return_args(*args):
    return args

return_args(*(calc(x) for x in steps))
#>>> ((2, 1), (4, 4), (6, 9), (8, 16), (10, 25))

因此,所有项目一次都在内存中。


那么我的工作原理是怎样的呢?

map(calc, steps)(calc(x) for x in steps)(Python 3) 相同。这是一个迭代器。在 Python 2 上,使用imapor (calc(x) for x in steps)

tee(..., 2)获得两个存储迭代差异的迭代器。如果您以同步方式迭代,tee则将占用O(1)内存。如果不这样做,则tee最多可以占用O(n). 所以现在我们有一个用法,可以让我们拥有到目前为止的O(1)内存。

enumerate显然会将其保存在不断的记忆中。

(r[i] for r in results)返回一个迭代器,它从每个结果中获取第ith项。这意味着在这种情况下,它会收到一对(依次为 so r=(2,1)、等)。r=(4,4)它返回特定的迭代器。

因此,如果您进行迭代ay并且by步调一致,将使用常量内存。内存使用量与迭代器之间的距离成正比。这在许多情况下很有用(想象一下对文件进行比较或诸如此类),但正如我大部分时间所说的那样,它不值得丑陋。还有一个额外的常数因子开销。

于 2013-09-29T19:15:49.627 回答
2

你应该向我们展示什么

[calc(s) for s in xrange(5)]

确实给你,即

[(0, 0), (2, 1), (4, 4), (6, 9), (8, 16)]

虽然它不是您想要的 2 个列表,但它仍然是一个列表列表。更何况,这看起来不像吗?

zip((0, 2, 4, 6, 8), (0, 1, 4, 9, 16))

zip重新打包一组列表。通常它用 2 个较长的列表来说明,但它与许多短列表一样有效。

第三步要记住fn(*[arg1,arg2, ...]) = fn(arg1,arg2, ...),即*解包一个列表。

将所有内容放在一起以获得 hcwhsa 的答案。

于 2013-09-29T20:22:50.587 回答