Почему я получаю MemoryError с itertools.product?
Я ожидал бы, что следующий фрагмент даст мне итератор, дающий пары из декартового произведения двух входных итераций:
$ 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 ответа
Он не хранит промежуточные результаты, но должен хранить входные значения, потому что каждое из них может понадобиться несколько раз для нескольких выходных значений.
Поскольку вы можете выполнять итерацию только один раз, 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
ты видишь.
itertools.product
не хранит промежуточные продукты в памяти, но сохраняет tuple
версии оригинальных итераторов.
Это можно увидеть, посмотрев на источник itertools
модуль. Это в файле Modules/itertoolsmodule.c
в исходном дистрибутиве Python 2.7.2. Там мы находим в функции product_new
(в основном конструктор для product
объект) от строки 1828 года:
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;
}
В этом коде args
аргументы product
, В третьей строке этого фрагмента кода i
Этот аргумент преобразуется в кортеж. Следовательно, код пытается конвертировать ваш итератор xrange(0, 10**9)
в кортеж, в результате чего MemoryError
,
Я не уверен почему itertools.product
ведет себя так Вместо того, чтобы хранить каждый входной итератор как кортеж, этого должно быть достаточно для сохранения последнего элемента, возвращаемого каждым итератором. (РЕДАКТИРОВАТЬ: см. Ответ чф по причине)
Я думаю, что проблема может заключаться в том, что xrange возвращает свой особый вид объекта, который не является итеративным.
xrange реализован таким образом (как списки), что вы можете многократно повторять объект, в то время как вы можете перебирать обычный объект-генератор только один раз. Так что, возможно, что-то из этого функционала отвечает за ошибку памяти.