记录 first-class-assigned 个函数

Documenting first-class-assigned functions

我有一个利用Python函数的first-class性质定义的函数,如下:

add_relative = np.frompyfunc(lambda a, b: (1 + a) * (1 + b) - 1, 2, 1)

要么我需要一种方法将文档字符串添加到定义的函数中,要么使用更常见的格式实现相同的目的,以便我可以以正常方式编写文档字符串:

def add_relative(a, b):
    """
    Docstring
    """
    return np.frompyfunc(lambda a, b: (1 + a) * (1 + b) - 1, 2, 1)(a, b)

当函数被调用时有效

 add_relative(arr1, arr2)

但是我失去了调用方法的能力,例如

add_relative.accumulate(foo_arr, dtype=np.object)

我猜这是因为函数在使用 frompyfunc 时变得更像 class,是从 ufunc.

派生的

我想我可能需要定义一个 class,而不是一个函数,但我不确定如何定义。我会同意的,因为这样我就可以像往常一样轻松添加文档字符串。

我标记了这个 coding-style 因为原来的方法有效但不容易记录下来,如果标题不清楚我很抱歉,我不知道正确的词汇来描述这个.

更新 1: 关闭,但这还不够好。因为装饰函数的 __doc__ 属性无法更新,而且因为 Sphinx 仍然只提取装饰函数的文档字符串,所以这并不能解决我的问题。

更新二: 我在下面提出的解决方案非常适合源代码中的文档。对于 Sphinx 的文档,我最终只是用

覆盖了文档字符串
.. function:: sum_relative(a, b)

   <Docstring written in rst format>

它很丑陋,很老套,而且是手动的,但这意味着我在源代码中有我很好的文档,我在 Sphinx 中有我很好的文档。

所有问题都源于 numpy.ufunc__doc__ 属性是不可变的。如果有人知道为什么,我很想听听为什么。我猜测它来自用 C 编写的东西,而不是纯粹的 Python。无论如何,这很烦人。


我发现我可以使用装饰器来解决问题 np.frompyfunc()

我编写基本函数(原始示例中的 lambda)并正常添加文档字符串,然后应用装饰器:

def as_ufunc(func):
    return np.frompyfunc(func, 2, 1)

@as_ufunc
def sum_relative(a, b):
    """
    Docstring
    """
    return (1 + a) * (1 + b) - 1

由于以下原因,这不是一个完美的解决方案:

  • sum_relative.__doc__frompyfunc 覆盖为通用且无用的文档字符串。我在这里不介意,因为我真的很关心从文档字符串中使用 Sphinx 生成的文档,而不是以编程方式访问它。您可能会想尝试 functools.wrapsfunctools.update_wrapper,但是 numpy.ufunc__doc__ 成员显然是不可变的。

  • 我必须对 frompyfunc 的后两个参数进行硬编码。我在这里不介意,因为我在这里使用它的所有情况都需要相同的值。

  • 编辑:上面的点是可以绕过的,有点冗长,但不多:

    def as_ufunc(nin, nout):
        def _decorator(func):
            return np.frompyfunc(func, nin, nout)
        return _decorator
    
    @as_ufunc(2, 1)
    def sum_relative(a, b):
        """
        Docstring
        """
        return (1 + a) * (1 + b) - 1
    
  • 它比原来的解决方案更冗长。我不介意,因为我现在有了文档字符串。

我认为这样的方法可能有效:

UFUNC_ATTRS = (
    'nin',
    'accumulate',
    # etc...
)


def redirectattribtues(destination):
    def decorator(func):
        for attribute in UFUNC_ATTRS:
            setattr(func, attribute, getattr(destination, attribute))
        return func
    return decorator


ufunc = np.frompyfunc(lambda a, b: (1 + a) * (1 + b) - 1, 2, 1)


@redirectattribtues(destination=ufunc)
def add_relative(a, b):
    """
    Docstring
    """
    return ufunc(a, b)


# test
arr1 = np.array(list(range(0, 10)))
arr2 = np.array(list(range(10, 20)))
print(add_relative(arr1, arr2))
print(add_relative.accumulate(arr1, dtype=object))