为啥使用 itertools.product 时会出现 MemoryError?

Posted

技术标签:

【中文标题】为啥使用 itertools.product 时会出现 MemoryError?【英文标题】:Why do I get a MemoryError with itertools.product?为什么使用 itertools.product 时会出现 MemoryError? 【发布时间】:2012-01-01 20:48:17 【问题描述】:

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

$ 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

【问题讨论】:

【参考方案1】:

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

由于你只能在一个迭代器上迭代一次,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

【讨论】:

感谢您填写实施的动机。我想解决这个问题的唯一其他方法是坚持提供的可迭代对象也可以以某种方式复制。 我找到了问题的根源。但是,如果确实需要 product() 的功能,有什么解决方法? @DSR 你找到了吗?【参考方案2】:

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 的参数。在这段代码的第三行,ith 参数被转换为一个元组。因此,代码尝试将您的迭代器 xrange(0, 10**9) 转换为元组,从而生成 MemoryError

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

【讨论】:

这很有趣。我想对于这么简单的事情,我可以构建自己的生成器。【参考方案3】:

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

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

【讨论】:

以上是关于为啥使用 itertools.product 时会出现 MemoryError?的主要内容,如果未能解决你的问题,请参考以下文章

itertools.product 比嵌套 for 循环慢

Python for 循环偏移 (Itertools.product)

itertools.product 消除重复元素

Itertools.product 自定义每个输入的组合数量?

itertools.product - 返回列表而不是元组

Python小技巧:使用*解包和itertools.product()求笛卡尔积(转)