从列表推导式和一般情况下高效地创建numpy数组

47

在我的当前工作中,我经常使用Numpy和列表理解,并为了获得最佳性能,我有以下问题:

如果我按照以下方式创建一个Numpy数组,实际上会发生什么?

a = numpy.array( [1,2,3,4] )

我猜测Python首先创建一个包含值的普通列表,然后使用列表大小来分配NumPy数组,并将值复制到这个新数组中。这是正确的吗,还是解释器足够聪明,能够意识到该列表仅是中间过程,并直接复制值?

类似地,如果我想要使用numpy.fromiter()从列表推导式创建NumPy数组:

a = numpy.fromiter( [ x for x in xrange(0,4) ], int )

这会导致在输入 fromiter() 之前创建一个中间值列表吗?


2
如果您想避免创建列表,为什么不使用a = numpy.fromiter(xrange(4), int)而是使用a = numpy.fromiter([x for x in xrange(0,4)], int) - wim
2
@wim 或只需 np.arange ... - Jon Clements
仅举一个例子(我承认这个例子很差)。表达式可以是任何东西。 - NielsGM
2
@wim 提出的观点是,numpy.fromiter(list(something), ...numpy.fromiter([something], ... 绝对不应该被使用!无论 something 是什么,都应该始终使用 numpy.fromiter(something, ... - Stefano M
相关链接:https://dev59.com/qHRC5IYBdhLWcg3wP-Zh - 0 _
显示剩余2条评论
3个回答

47

我相信你所寻找的答案是使用 生成器表达式numpy.fromiter

numpy.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)

生成器表达式是惰性求值的——只有在迭代时才会计算表达式的值。

使用列表推导式会先创建列表,然后将其传递到numpy中,而生成器表达式每次只会产生一个值。

Python按照内部->外部的方式评估表达式,与大多数编程语言相同(如果不是全部),因此使用[<something> for <something_else> in <something_different>]将创建列表,然后对其进行迭代。


@JonClements 你可以对 x 应用一些函数,需要时会进行评估。 - Snakes and Coffee
20
NumPy需要知道生成器的大小以为其分配内存。np.fromiter如何处理呢?是将生成的值存储起来,从而失去了不生成列表或元组的目的吗?还是运行生成器两次,一次计数,另一次填充数组? - Jaime
1
@Jaime 根据文档,如果您将大小指定为“count”,那么numpy将预先分配内存 - 因此,如果您已经拥有它,则可以这样做。否则,您是正确的 - 它必须运行生成器并计算它所做的列表。 - Snakes and Coffee
3
@ Jaime:生成器只需要运行一次!(考虑副作用等因素)。我没有阅读过 numpy 中的 fromiter 的源代码,但肯定 numpy.fromiter(something, int)numpy.fromiter(list(something), int) 更高效。numpy 可以使用 malloc/realloc 来创建一个大小为 sizeof(int) 的对象数组。在 CPython 中,一个列表是一个异构对象的可变集合,因此它具有更复杂的数据结构和分配策略。 - Stefano M
8
文档中已经非常清楚了。指定计数有助于提高性能。这使得fromiter可以预先分配输出数组,而不是按需重新调整大小。 当达到容量时,它会重新分配数组。这类似于C++中的std::vector的行为。 - Cron Merdek
显示剩余2条评论

8
你可以创建自己的列表并进行实验,以揭示情况的真相...
>>> class my_list(list):
...     def __init__(self, arg):
...         print 'spam'
...         super(my_list, self).__init__(arg)
...   def __len__(self):
...       print 'eggs'
...       return super(my_list, self).__len__()
... 
>>> x = my_list([0,1,2,3])
spam
>>> len(x)
eggs
4
>>> import numpy as np
>>> np.array(x)
eggs
eggs
eggs
eggs
array([0, 1, 2, 3])
>>> np.fromiter(x, int)
array([0, 1, 2, 3])
>>> np.array(my_list([0,1,2,3]))
spam
eggs
eggs
eggs
eggs
array([0, 1, 2, 3])

2
针对标题中的问题,现在有一个名为numba的软件包,支持numpy数组推导,可以直接构造numpy数组而无需使用中间的python列表。与numpy.fromiter不同,它还支持嵌套推导。但是,请注意如果您不熟悉它,numba存在一些限制和性能怪异问题。
话虽如此,如果您可以使用numpy的向量操作编写代码,则保持简单可能更好。
>>> from timeit import timeit
>>> # using list comprehension
>>> timeit("np.array([i*i for i in range(1000)])", "import numpy as np", number=1000)
2.544344299999999
>>> # using numpy operations
>>> timeit("np.arange(1000) ** 2", "import numpy as np", number=1000)
0.05207519999999022
>>> # using numpy.fromiter
>>> timeit("np.fromiter((i*i for i in range(1000)), dtype=int, count=1000)",
...        "import numpy as np",
...        number=1000)
1.087984500000175
>>> # using numba array comprehension
>>> timeit("squares(1000)",
... """
... import numpy as np
... import numba as nb
... 
... @nb.njit
... def squares(n):
...     return np.array([i*i for i in range(n)])
... 
... 'compile the function'
... squares(10)
... """,
... number=1000)
0.03716940000003888

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接