Python 使矢量化numpy函数的行为类似于ufunc

Python 使矢量化numpy函数的行为类似于ufunc,python,numpy,cython,Python,Numpy,Cython,假设我们有一个Python函数,它接受Numpy数组并返回另一个数组: import numpy as np def f(x, y, method='p'): """Parameters: x (np.ndarray) , y (np.ndarray), method (str) Returns: np.ndarray""" z = x.copy() if method == 'p': mask = x < 0 else:

假设我们有一个Python函数,它接受Numpy数组并返回另一个数组:

import numpy as np

def f(x, y, method='p'):
    """Parameters:  x (np.ndarray) , y (np.ndarray), method (str)
    Returns: np.ndarray"""
    z = x.copy()    
    if method == 'p':
        mask = x < 0
    else:
        mask = x > 0
    z[mask] = 0
    return z*y
  • 无法更改函数
    f
    ,如果为
    x
    y
    指定标量参数,则函数将失败

  • x
    y
    是标量时,我们将它们转换为一维数组,进行计算,然后在最后将它们转换回标量

  • f
    针对数组进行了优化,标量输入主要是一种方便。因此,编写一个使用标量的函数,然后使用
    np.vectorize
    np.frompyfunc
    是不可接受的
实施的开始可以是

def atleast_1d_inverse(res, x):
    # this function fails in some cases (see point 1 below).
    if res.shape[0] == 1:
        return res[0]
    else:
        return res

def ufunc_wrapper(func, args=[]):
    """ func:  the wrapped function
        args:  arguments of func to which we apply np.atleast_1d """

    # this needs to be generated dynamically depending on the definition of func
    def wrapper(x, y, method='p'):
        # we apply np.atleast_1d to the variables given in args
        x = np.atleast_1d(x)
        y = np.atleast_1d(x)

        res = func(x, y, method='p')

        return atleast_1d_inverse(res, x)

    return wrapper

f_ufunc = ufunc_wrapper(f, args=['x', 'y'])
它基本上是有效的,但是会通过上面的测试2,产生一个标量输出而不是向量输出。如果我们想解决这个问题,我们需要在输入类型上添加更多的测试(例如,
isinstance(x,np.ndarray)
x.ndim>0
,等等),但我恐怕会忘记一些常见的情况。此外,上述实现不够通用,无法用不同数量的参数包装函数(请参见下面的第2点)

在使用Cython/f2py函数时,这似乎是一个相当常见的问题,我想知道是否有一个通用的解决方案

编辑:在@hpaulj的评论之后再精确一点。基本上,我在寻找

  • 一种至少与np相反的函数,如
    至少\u 1d\u逆(np.至少\u 1d(x),x)=x
    ,其中第二个参数仅用于确定原始对象的类型或维数
    x
    。返回numpy标量(即ndim=0的数组)而不是python标量是可以的

  • 检查函数f并生成与其定义一致的包装器的方法。例如,这种包装器可以用作

    f_ufunc=ufunc_包装(f,args=['x','y'])

    然后,如果我们有一个不同的函数
    def2(x,option=2):返回x**2
    ,我们也可以使用

    f2\u ufunc=ufunc\u包装(f2,args=['x'])


  • 注意:与UFUNC的类比可能有点有限,因为这对应于相反的问题。我没有一个标量函数,我们将其转换为接受向量和标量输入,而是设计了一个用于处理向量的函数(可以看作是以前被向量化的东西),我想再次接受标量,不更改原始函数。

    这并不能完全回答使矢量化函数真正像
    ufunc
    那样工作的问题,但我最近确实遇到了与您的问题类似的
    numpy.vectorize
    的问题。即使传递了标量输入,该包装器仍坚持返回一个
    数组(ndim=0
    shape=()

    但似乎以下做法是正确的。在本例中,我对一个简单函数进行矢量化,以将浮点值返回到一定数量的有效数字

    def signif(x, digits):
        return round(x, digits - int(np.floor(np.log10(abs(x)))) - 1)
    
    def vectorize(f):
        vf = np.vectorize(f)
    
        def newfunc(*args, **kwargs):
            return vf(*args, **kwargs)[()]
        return newfunc
    
    vsignif = vectorize(signif)
    
    这给

    >>> vsignif(0.123123, 2)
    0.12
    >>> vsignif([[0.123123, 123.2]], 2)
    array([[   0.12,  120.  ]])
    >>> vsignif([[0.123123, 123.2]], [2, 1])
    array([[   0.12,  100.  ]])
    

    请记住
    ufunc
    不会返回真正的标量
    np.add(1,1)
    返回一个
    numpy.int32
    ,形状
    ()
    。它可能使用了
    np.asarray
    或等效物。您能想到任何现有的
    ufunc
    需要转换,如
    至少\u 1d
    。如果
    asarray
    足够强大,那么您可能正在将
    ufunc
    类比推向未知领域。@hpaulj感谢您的评论。是的,当我说标量时,numpy标量(ndim==0的数组)也可以。我对上述问题进行了更详细的编辑,以回应您的评论。类似:
    >>> vsignif(0.123123, 2)
    0.12
    >>> vsignif([[0.123123, 123.2]], 2)
    array([[   0.12,  120.  ]])
    >>> vsignif([[0.123123, 123.2]], [2, 1])
    array([[   0.12,  100.  ]])