Как мне построить массив из генератора?

166

Как я могу построить массив из объекта генератора?

Позвольте мне проиллюстрировать проблему:

>>> 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())), но я не хочу платить за использование промежуточного списка и окончательного массива в памяти одновременно. Есть ли более экономичный способ?

saffsd
источник
6
Это интересная проблема. Я столкнулся с этим путем from numpy import *; print any(False for i in range(1))- который затеняет встроенное any()и дает противоположный результат (как я знаю сейчас).
moooeeeep
4
@moooeeeep это ужасно. если numpyне может (или не хочет) обращаться с генераторами, как это делает Python, по крайней мере, он должен вызвать исключение, когда получает генератор в качестве аргумента.
максимум
1
@max Я наступил точно так же, как мой. По-видимому, это было поднято в списке NumPyранее ), заключив, что это не будет изменено, чтобы вызвать исключение, и всегда следует использовать пространства имен.
Алексей

Ответы:

128

Для массивов Numpy их длина должна быть задана явно во время создания, в отличие от списков Python. Это необходимо для того, чтобы пространство для каждого элемента могло быть последовательно выделено в памяти. Последовательное распределение является ключевой особенностью массивов numpy: это в сочетании с реализацией собственного кода позволяет выполнять над ними операции намного быстрее, чем обычные списки.

Помня об этом, технически невозможно взять объект-генератор и превратить его в массив, если только вы не:

  1. может предсказать, сколько элементов он выдаст при запуске:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
  2. готовы хранить его элементы в промежуточном списке:

    my_array = numpy.array(list(gimme()))
  3. Можно создать два идентичных генератора, выполнить первый, чтобы найти общую длину, инициализировать массив, а затем снова запустить генератор, чтобы найти каждый элемент:

    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 - неэффективное время (вы должны пройти через генератор дважды).

shsmurfy
источник
11
Встроенный array.arrayсписок представляет собой непрерывный несвязанный список, и вы можете просто array.array('f', generator). Сказать, что это невозможно, вводит в заблуждение. Это просто динамическое распределение.
Cuadue
1
Почему numpy.array не выделяет память так же, как встроенный array.array, как говорит Cuadue. Что такое торговля? Я спрашиваю, потому что в обоих примерах есть непрерывная выделенная память. Или не?
jgomo3
3
Numpy предполагает, что размеры его массива не изменяются. Он сильно зависит от разных представлений одного и того же куска памяти, поэтому для расширения и перераспределения массивов потребуется, например, дополнительный уровень косвенности для включения представлений.
Джоэлн
2
Использование пустых немного быстрее. Поскольку вы собираетесь инициализировать значения любым способом, нет необходимости делать это дважды.
Каушик Гхосе
Смотрите также ответ @ dhill ниже, который быстрее, чем 1.
Билл
206

Один гугл за этим результатом переполнения стека, я обнаружил, что есть numpy.fromiter(data, dtype, count) . По умолчанию count=-1берут все элементы из итерируемого. Требуется dtypeустановить явно. В моем случае это сработало:

numpy.fromiter(something.generate(from_this_input), float)

dhill
источник
как бы вы применили это к вопросу? numpy.fromiter(gimme(), float, count=-1)не работает. Что означает something?
Матиас 009
1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)работает для меня.
moooeeeep
14
Поток, объясняющий, почему fromiterработает только на одномерных массивах: mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
максимум
2
fwiw, указывать count=-1не нужно, так как это значение по умолчанию.
askewchan
5
Если вы знаете длину итерируемого заранее, укажите countдля повышения производительности. Таким образом, он выделяет память перед заполнением ее значениями, а не изменяет размеры по требованию (см. Документацию numpy.fromiter)
Eddy
15

Хотя вы можете создать массив 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]. Реализация может быть найдена здесь .

mdeff
источник
1
Это аккуратное решение, спасибо за указание. Но это, кажется, немного медленнее (в моем приложении), чем использование np.array(tuple(mygen)). Вот результаты теста: %timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per loopпо сравнению с%timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
Билл
13
Это кажется отличным и работает для меня. Но с Numpy 1.16.1 я получаю это предупреждение:FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
Джозеф Шиди
6

В некоторой степени тангенциально, но если ваш генератор предназначен для понимания списков, вы можете использовать его numpy.whereдля более эффективного получения результата (я обнаружил это в своем собственном коде после просмотра этого поста)

Бенджамин Хорстман
источник
0

Функции vstack , hstack и dstack могут принимать в качестве входных генераторов, которые выдают многомерные массивы.

Майк Р
источник
3
Можете привести пример, если ссылки меняются или что-то еще? :)
Ари Купер-Дэвис
Эти функции могут принимать генератор массивов, а не генератор значений
retnikt