如何从生成器构建numpy数组?


166

如何从生成器对象构建numpy数组?

让我说明一下这个问题:

>>> 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()是我想将其输出转换为数组的生成器。但是,数组构造函数不会迭代生成器,它只是存储生成器本身。我想要的行为是from的numpy.array(list(gimme())),但是我不想支付同时拥有中间列表和最终数组的内存开销。有没有更节省空间的方法?


6
这是一个有趣的问题。我遇到了这个问题from numpy import *; print any(False for i in range(1))-遮盖了内置的东西any()并产生了相反的结果(如我现在所知)。
moooeeeep 2012年

4
@moooeeeep太糟糕了。如果numpy不能(或不想)像Python那样对待生成器,则至少在接收到生成器作为参数时,它应该引发异常。
最多

1
@max我踩到了完全相同的地雷。显然,这是在NumPy列表(及更早版本提出的结论是不会更改以引发异常,并且应该始终使用名称空间。
alexei 2014年

Answers:


128

与python列表不同,numpy数组要求在创建时明确设置其长度。这是必需的,以便可以在内存中连续分配每个项目的空间。连续分配是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)。说不可能是一种误导。这只是动态分配。
Cuadue

1
正如Cuadue所说,为什么numpy.array不能以与内置array.array相同的方式进行内存分配。有什么权衡?我问,因为在两个示例中都有连续的分配内存。或不?
jgomo3

3
numpy假定其数组大小不变。它严重依赖于同一块内存的不同视图,因此,例如,允许扩展和重新分配阵列将需要附加的间接层来启用视图。
joeln

2
使用空有点更快。由于您将以任何方式初始化值,因此无需重复两次。
Kaushik Ghose

另请参见下面的@dhill答案,该答案比1更快。–
Bill

206

这个stackoverflow结果背后的一个Google,我发现有一个numpy.fromiter(data, dtype, count)。默认值count=-1从可迭代中获取所有元素。它需要dtype明确设置。就我而言,这可行:

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


您如何将其应用于问题?numpy.fromiter(gimme(), float, count=-1)不起作用。代表什么something
Matthias 009年

1
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)为我工作。
moooeeeep 2012年

14
解释为什么fromiter只对一维数组起作用的线程:mail.scipy.org/pipermail/numpy-discussion/2007-August/…
最多

2
fwiw,count=-1不需要指定,因为它是默认值。
askewchan

5
如果事先知道可迭代的长度,请指定count以提高性能。这样,它会在用值填充之前分配内存,而不是按需调整大小(请参阅numpy.fromiter
Eddy

15

虽然可以使用生成器创建一维数组numpy.fromiter(),但可以使用生成器创建ND数组numpy.stack

>>> mygen = (np.ones((5, 3)) for _ in range(10))
>>> x = numpy.stack(mygen)
>>> x.shape
(10, 5, 3)

它也适用于一维数组:

>>> 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
Bill

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.
Joseph Sheedy

6

有点切线,但是如果生成器是列表理解器,则可以numpy.where用来更有效地获取结果(我在看完这篇文章后在自己的代码中发现了此结果)


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.