为什么'...->nil'被禁止作为np.vectorize签名?



当你想写一个函数,只做副作用,并不想实际返回一个结果数组(打印一些东西或回调方法或函数上存储的对象),你不能使用np.vectorize(f, signature='...->nil')(其中...是输入签名取决于函数)

的例子:

def f(x, y):
print(f'{x=} {y=}')
return
F = np.vectorize(f, signature='(m),(m)->()') # Works but returns an array containing references to None...
F = np.vectorize(f, signature='(m),(m)->nil') # Won't work
a = np.arange(2*10).reshape((10,2))
F(a, [0,0])

为什么有这样的限制?

虽然我已经回答了一些关于在np.vectorize中使用signature的问题,但我没有注意https://numpy.org/doc/stable/reference/c-api/generalized-ufuncs.html, generalized-ufuncs链接。我的印象是,signature是在gufuncs标准完全开发之前添加到vectorize的。我同意@Michael的观点,在该文档中nil的使用是不明确的。

使用nil时的错误信息是:

In [12]: F = np.vectorize(f, signature='(m),(m)->nil')
Traceback (most recent call last):
Input In [12] in <cell line: 1>
F = np.vectorize(f, signature='(m),(m)->nil')
File /usr/local/lib/python3.8/dist-packages/numpy/lib/function_base.py:2272 in __init__
self._in_and_out_core_dims = _parse_gufunc_signature(signature)
File /usr/local/lib/python3.8/dist-packages/numpy/lib/function_base.py:2000 in _parse_gufunc_signature
raise ValueError(
ValueError: not a valid gufunc signature: (m),(m)->nil

np.lib.function_base._parse_gufunc_signature是一个python函数,它使用re来解析字符串。解析字符串为:

In [19]: np.lib.function_base._SIGNATURE
Out[19]: '^\((?:\w+(?:,\w+)*)?\)(?:,\((?:\w+(?:,\w+)*)?\))*->\((?:\w+(?:,\w+)*)?\)(?:,\((?:\w+(?:,\w+)*)?\))*$'

没有'nil'

对于有效情况,解析结果为:

In [20]: np.lib.function_base._parse_gufunc_signature('(m),(m)->()')
Out[20]: ([('m',), ('m',)], [()])

在Python中,所有函数都返回一些东西,即使它只有None。这对于print是正确的(在Py2中是命令/语句,而不是函数)。None是一个Python对象,特殊,但与c中的nil/null不同。

所以对我来说,F应该返回一个充满None的数组是合乎逻辑的,形状决定了广播的尺寸(减去后面的m)

因此a(3,1)与a(2,)广播到(3,2),并返回a(3,),以及对f的3次调用:

In [22]: F(np.array([[5],[9],[8]]),np.array([8.9]))
x=array([5]) y=array([8.9])
x=array([9]) y=array([8.9])
x=array([8]) y=array([8.9])
Out[22]: array([None, None, None], dtype=object)

F返回的数组可以忽略。

vectorizesignatured文档:

如果提供,pyfunc将被调用(并预计返回)数组的形状由相应核心维的大小给出。

f返回一个标量None。就签名形状而言,这与返回标量字符串或数字没有什么不同。

vectorize是Python代码,所以可以学习。多年来,我一直在关注它,它变得越来越复杂。最初我发现它总是比等价的python迭代或列表推导要慢。在较新的测试中,我发现它的可扩展性更好,因此对于非常大的输入,它可以更快。它仍然没有真正的,编译的,numpy矢量化。

在简单的情况下,vectorize实际上使用frompyfunc,它返回一个objectdtype数组。然后将该输出转换为otypes输出。frompyfunc被编译了(虽然它不编译你的pyfunc),而且它本身比vectorize更快。

对于signature,vectorize采用不同的路由。这是文档警告,这是较慢的

_vectorize_call_with_signature的核心是ndindex的迭代:

for index in np.ndindex(*broadcast_shape):
results = func(*(arg[index] for arg in args))

简而言之,np.vectorize不是一个性能工具,特别是对于signature。但它仍然可能是有用的,特别是当您有几个输入参数,并希望完整的broadcasting功能时。

最新更新