Як створити масивний масив з генератора?


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())), але я не хочу платити за обсяг пам'яті, щоб мати проміжний список і остаточний масив в пам'яті одночасно. Чи є більш ефективний простір спосіб?


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 - неефективний у часі (вам доведеться пройти через генератор двічі).


11
Вбудований array.array- це суміжний не пов'язаний список, і ви можете просто array.array('f', generator). Сказати, що це неможливо, вводить в оману. Це просто динамічне розподіл.
Куадуе

1
Чому numpy.array не здійснює розподіл пам'яті так само, як вбудований array.array, як говорить Cuadue. Що таке торгівля? Я запитую, оскільки в обох прикладах є суміжна виділена пам'ять. Чи ні?
jgomo3

3
numpy передбачає, що його розміри масиву не змінюються. Він значною мірою покладається на різні погляди одного і того ж фрагмента пам'яті, тому для розширення та перерозподілу масивів потрібен буде додатковий шар непрямості, наприклад, для перегляду.
joeln

2
Використання порожнього - трохи швидше. Оскільки ви збираєтесь ініціалізувати значення будь-яким способом, не потрібно робити цього двічі.
Kaushik Ghose

Дивіться також відповідь @ dhill, нижче якої швидше, ніж 1.
Білл

206

Один google за цим результатом stackoverflow виявив, що існує a numpy.fromiter(data, dtype, count) . За замовчуванням count=-1приймаються всі елементи з ітерабельного. Для цього dtypeпотрібно встановити явно. У моєму випадку це спрацювало:

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


як би ви застосували це до питання? numpy.fromiter(gimme(), float, count=-1)не працює. Що означає something?
Маттіас 009,

1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)працює для мене.
moooeeeep

14
Потік, що пояснює, чому fromiterпрацює лише 1D масиви: mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
макс

2
fwiw, count=-1не потрібно вказувати, оскільки це за замовчуванням.
askewchan

5
Якщо ви заздалегідь знаєте тривалість перегляду, вкажіть, countщоб покращити продуктивність. Таким чином, він виділяє пам'ять, перш ніж заповнити її значеннями, а не змінювати розмір на вимогу (див. Документацію numpy.fromiter)
Едді

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]. Реалізацію можна знайти тут .


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.