为什么我用itertools.product得到一个MemoryError?

时间:2012-01-01 20:48:18

标签: python itertools

我希望以下代码片段给出一个迭代器,从两个输入迭代的笛卡尔积中得到对:

$ 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

3 个答案:

答案 0 :(得分:16)

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

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

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

如果此处b是迭代器,则在外循环的第一次迭代后它将耗尽,并且在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

答案 1 :(得分:7)

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

通过查看itertools模块的来源可以看出这一点。它位于Python 2.7.2源代码分发中的文件Modules/itertoolsmodule.c中。我们在第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的参数。在此代码段的第三行中,i参数将转换为元组。因此,代码尝试将迭代器xrange(0, 10**9)转换为元组,从而生成MemoryError

我不确定为什么itertools.product表现得像这样。不是将每个输入迭代器存储为元组,而是应该足以存储从每个迭代器返回的最后一个项。 (编辑:看到某人的答案)

答案 2 :(得分:0)

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

xrange以这样的方式实现(如列表一样),您可以多次遍历对象, 而你只能迭代一次普通的生成器对象。因此,此功能可能会导致内存错误。