将Python生成器解包为参数 - 节省内存吗?

3 投票
2 回答
3931 浏览
提问于 2025-04-18 17:32

假设我有一个生成器,用来生成集合

def f(n) :
  for i in xrange(n) :
    yield set(xrange(i) )

>>> for s in f(5) :
      print s

set([])
set([0])
set([0, 1])
set([0, 1, 2])
set([0, 1, 2, 3])

现在我想把这些集合合并在一起,也就是做并集。我可以先创建一个临时的集合列表,然后把这个列表里的内容传给并集

>>> set.union( * list( f(5) ) )
set([0, 1, 2, 3])

我也可以直接把生成器传给并集

>>> set.union( * f(5) )
set([0, 1, 2, 3])

那么第二种方法会像第一种那样创建一个完整的临时列表吗?哪种方法更节省内存呢?

2 个回答

4

这两种方法都会计算(并存储)生成器中的所有元素。当你调用一个函数时,所有的参数都必须在函数被调用之前先计算出来。

我们可以通过一个简单的例子来看这个问题:

def f(n):
    for i in xrange(n) :
        yield set(xrange(i) )
    1/0

def blah(*args):
    print "Blah!"

>>> blah(*f(5))
Traceback (most recent call last):
  File "<pyshell#56>", line 1, in <module>
    blah(*f(5))
  File "<pyshell#52>", line 4, in f
    1/0
ZeroDivisionError: division by zero

注意到“Blah!”没有被打印出来。因为在尝试计算生成器 f(5) 中的所有元素时发生了异常,所以 blah 这个调用根本没有执行。

11

在Python中,当你把生成器作为参数使用时,它会展开生成器;这意味着在调用之前,生成器产生的所有值都会被加载到内存中,无论你选择哪种方式。

你也可以使用reduce()函数来代替:

from functools import reduce  # Python 3 forward compatibility

reduce(set.union, f(5))

这样做会一个一个地遍历f(5)产生的值,而不是先把它们全部放在一起。

演示:

>>> def f(n):
...     for i in xrange(n):
...         yield set(xrange(i))
... 
>>> reduce(set.union, f(5))
set([0, 1, 2, 3])

撰写回答