Как мне построить массив из генератора?
Как я могу построить массив из объекта генератора?
Позвольте мне проиллюстрировать проблему:
>>> import numpy
>>> def gimme():
... for x in xrange(10):
... yield x
...
>>> gimme()
<generator object at 0x28a1758>
>>> list(gimme())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numpy.array(xrange(10))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> numpy.array(gimme())
array(<generator object at 0x28a1758>, dtype=object)
>>> numpy.array(list(gimme()))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
В этом случае gimme() - это генератор, чей вывод я бы хотел превратить в массив. Однако конструктор массива не выполняет итерацию по генератору, он просто хранит сам генератор. Мне нужно поведение из numpy.array(list(gimme())), но я не хочу платить за использование промежуточного списка и окончательного массива в памяти одновременно. Есть ли более экономичный способ?
5 ответов
Для массивов Numpy их длина должна быть задана явно во время создания, в отличие от списков Python. Это необходимо для того, чтобы место для каждого элемента могло быть последовательно выделено в памяти. Последовательное распределение является ключевой особенностью массивов numpy: это в сочетании с реализацией собственного кода позволяет выполнять над ними операции намного быстрее, чем обычные списки.
Имея это в виду, технически невозможно взять объект-генератор и превратить его в массив, если только вы не:
может предсказать, сколько элементов он выдаст при запуске:
my_array = numpy.empty(predict_length()) for i, el in enumerate(gimme()): my_array[i] = el
готовы хранить его элементы в промежуточном списке:
my_array = numpy.array(list(gimme()))
Можно создать два идентичных генератора, выполнить первый, чтобы найти общую длину, инициализировать массив, а затем снова запустить генератор, чтобы найти каждый элемент:
length = sum(1 for el in gimme()) my_array = numpy.empty(length) for i, el in enumerate(gimme()): my_array[i] = el
1, вероятно, то, что вы ищете. 2 - неэффективное пространство, а 3 - неэффективное по времени (вы должны пройти через генератор дважды).
Один гугл за этим результатом переполнения стека, я обнаружил, что есть numpy.fromiter(data, dtype, count)
, По умолчанию count=-1
берет все элементы из повторяемого. Требуется dtype
быть установленным явно. В моем случае это сработало:
numpy.fromiter(something.generate(from_this_input), float)
В то время как вы можете создать 1D массив из генератора с numpy.fromiter()
, вы можете создать массив ND из генератора с numpy.stack
:
>>> mygen = (np.ones((5, 3)) for _ in range(10))
>>> x = numpy.stack(mygen)
>>> x.shape
(10, 5, 3)
Это также работает для 1D массивов:
>>> numpy.stack(2*i for i in range(10))
array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
Обратите внимание, что numpy.stack
внутренне потребляет генератор и создает промежуточный список с arrays = [asanyarray(arr) for arr in arrays]
, Реализация может быть найдена здесь.
Несколько тангенциально, но если ваш генератор является списком, вы можете использовать numpy.where
чтобы более эффективно получить ваш результат (я обнаружил это в своем собственном коде после просмотра этого поста)