13

我希望下面的代码片段给我一个迭代器,从两个输入迭代的笛卡尔积中产生对:

$ python
Python 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import itertools
>>> one = xrange(0, 10**9)
>>> two = (1,)
>>> prods = itertools.product(one, two)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError

相反,我得到一个MemoryError. 但我认为itertools.product没有将中间结果存储在内存中,那么是什么原因造成的MemoryError

4

3 回答 3

17

它不存储中间结果,但它必须存储输入值,因为每个输出值可能需要多次。

由于您只能在迭代器上迭代一次,product因此无法等效于以下实现:

def prod(a, b):
  for x in a:
    for y in b:
       yield (x, y)

如果 hereb是一个迭代器,它将在外循环的第一次迭代后耗尽,并且在后续执行中不会产生更多元素for y in b

product通过存储由 生成的所有元素来解决此问题b,以便它们可以重复使用:

def prod(a, b):
  b_ = tuple(b)  # create tuple with all the elements produced by b
  for x in a:
    for y in b_:
       yield (x, y)

事实上,product它会尝试存储所有给定它的可迭代对象产生的元素,即使它的第一个参数可以避免这种情况。该函数只需要遍历第一个可迭代对象一次,因此不必缓存这些值。但它无论如何都会尝试这样做,这会导致MemoryError你看到。

于 2012-01-01T21:32:47.457 回答
7

itertools.product不会将中间产品存储在内存中,但会存储tuple原始迭代器的版本。

这可以通过查看itertools模块的源代码来看出。它位于Modules/itertoolsmodule.cPython 2.7.2 源代码分发的文件中。从第 1828 行开始,我们在函数product_new(基本上是对象的构造函数)中找到:product

for (i=0; i < nargs ; ++i) {
    PyObject *item = PyTuple_GET_ITEM(args, i);
    PyObject *pool = PySequence_Tuple(item);
    if (pool == NULL)
        goto error;
    PyTuple_SET_ITEM(pools, i, pool);
    indices[i] = 0;
}

在该代码中,argsproduct. 在这段代码的第三行中,ith 参数被转换为一个元组。因此,代码尝试将您的迭代器转换xrange(0, 10**9)为元组,从而生成MemoryError.

我不确定为什么会有这样itertools.product的行为。与其将每个输入迭代器存储为元组,不如存储每个迭代器返回的最后一项就足够了。(编辑:原因见某事的答案)

于 2012-01-01T21:16:32.023 回答
0

我认为问题可能是 xrange 返回它自己的特殊类型的对象,这不是正常的可迭代对象。

xrange 以这样一种方式实现(如列表),您可以多次迭代对象,而您只能迭代普通生成器对象一次。所以也许这个功能的某些东西是造成内存错误的原因。

于 2012-01-01T21:20:20.017 回答