Документирование назначенных функций первого класса

У меня есть функция, определенная с использованием первоклассной природы функций Python, а именно:

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, производной от ufunc.

Я думаю, что мне может понадобиться определить класс, а не функцию, но я не уверен, как это сделать. Я был бы согласен с этим, потому что тогда я могу легко добавить строку документации, как обычно.

Я пометил это coding-style, потому что исходный метод работает, но просто не может быть легко задокументирован, и мне очень жаль, если название неясно, я не знаю правильного словаря, чтобы описать это.


person Sam    schedule 20.12.2016    source источник
comment
Я думаю, следует отметить, что функция, определенная первым способом, имеет строку документации, но я хочу добавить свою собственную/перезаписать ее (что невозможно для add_relative.__doc__ = 'string').   -  person Sam    schedule 20.12.2016
comment
К сожалению, невозможно определить класс с numpy.ufunc в качестве базового класса. При этом возникает ошибка TypeError: type 'numpy.ufunc' is not an acceptable base type. Я действительно не хочу писать целую функцию с нуля только для того, чтобы иметь возможность добавить строку документации, когда исходное определение функции работает отлично.   -  person Sam    schedule 20.12.2016
comment
В общем, строку документации функции, определенной первоначальным образом, можно обновить, присвоив атрибуту __doc__, но здесь это неуместно, так как этот атрибут неизменяем для numpy.ufunc.   -  person Sam    schedule 22.12.2016


Ответы (2)


Обновление 1: закрыть, но этого все еще недостаточно. Поскольку атрибут __doc__ декорированной функции не может быть обновлен, и поскольку Sphinx по-прежнему получает только строку документации декорированной функции, это не решает мою проблему.

Обновление 2: Решение, которое я предложил ниже, хорошо подходит для документации в исходном коде. Для документации с Sphinx я просто перезаписал строку документации с помощью

.. function:: sum_relative(a, b)

   <Docstring written in rst format>

Это уродливо, это хакерство и это руководство, но это означает, что у меня есть хорошая документация в исходном коде, и у меня есть хорошая документация в Sphinx.

Все проблемы связаны с тем, что атрибут __doc__ для numpy.ufunc неизменяем. Если кто-нибудь знает, почему, я хотел бы услышать, почему. Я предполагаю, что что-то связано с тем, что это происходит от чего-то, написанного на C, а не на чистом Python. Несмотря ни на что, это очень раздражает.


Я обнаружил, что могу решить проблему, используя декоратор для применения np.frompyfunc().

Я пишу базовую функцию (лямбда в исходном примере) и добавляю строку документации как обычно, а затем применяю декоратор:

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.wraps или functools.update_wrapper, однако член __doc__ numpy.ufunc, по-видимому, неизменяем.

  • Мне нужно жестко закодировать вторые два аргумента для 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
    
  • Это более подробно, чем исходное решение. Я не против, потому что теперь у меня есть строка документации.
person Sam    schedule 20.12.2016

Я думаю, что что-то вроде этого может работать:

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))
person LoveToCode    schedule 28.04.2020