Как документировать функции Python с помощью декораторов перегрузки/отправки?

Применение документации к функциям с несколькими диспетчерами

Я использую пакет multipledispatch в моде аналогично приведенному ниже примеру кода. Я хочу иметь возможность видеть текст docstring, когда я запрашиваю help(my_add) в командной строке Python, но вместо этого я вижу только информацию о декораторе.

Functools.wraps должен быть способ сделать это, но как?

Я просмотрел functools.wraps, и я уверен, что это то, что я хочу использовать. Я нашел примеры того, как его использовать, например это и это.

Но я до сих пор не совсем понимаю два вопроса:

  1. Как применить functools.wraps к внешним декораторам, которыми я не "владею".
  2. Как применить это конкретно к этому случаю с множественной отправкой, поскольку функция, которую я хочу обернуть, будет иметь несколько docstrings, связанных с одним и тем же именем функции.

Пример: создание украшенной функции

Ниже приведен пример, который поможет объяснить.

>>> from multipledispatch import dispatch
>>> @dispatch(str, str)
... def my_add(elem1, elem2):
...   '''A flavor of 'add' where two strings are concatenated.'''
...   return elem1 + ' ' + elem2
... 
>>> @dispatch(int, int)
... def my_add(elem1, elem2):
...   '''A flavor of 'my_add' where two strings are concatenated.'''
...   return elem1 + elem2
... 
>>> my_add('hey','you')
'hey you'
>>> my_add(4, 5)
9
>>> my_add(4.5, 6)

(детали трассировки удалены...)

KeyError: (<class 'float'>, <class 'int'>)
During handling of the above exception, another exception occurred:
NotImplementedError: Could not find signature for my_add: <float, int>

Я хотел показать эту ошибку и разные диспетчеры просто для того, чтобы показать, что эта часть работает так, как я этого хочу (ищу совпавший диспетчер и вызывая связанный с ним «аромат» функции).

Пример: Вызов help в оформленной функции не удался!

Но затем, если я попытаюсь просмотреть справку, вместо простого docstring, который я предоставил, я увижу строку документации, связанную с декоратором @dispatch.

>>> help(my_add)

Help on Dispatcher in module multipledispatch.dispatcher object:

my_add = class Dispatcher(builtins.object)
 |  Methods defined here:
 |  
 |  __call__(self, *args, **kwargs)
 |      Call self as a function.
 |  
 |  __getstate__(self)
 |  

и т. д.


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

>>> help(my_add(3, 5))

Help on int object:

class int(object)
 |  int(x=0) -> integer
 |  int(x, base=10) -> integer
 |  
 |  Convert a number or string to an integer, or return 0 if no arguments

person Mike Williamson    schedule 19.01.2018    source источник


Ответы (1)


Декоратор functools.wraps() должен быть частью реализации декоратора, и вы не можете применить его постфактум. Проблема здесь в том, что декоратор возвращает экземпляр пользовательского класса, а help() может отображать документацию только для класса этого экземпляра.

Экземпляр Dispatcher(), возвращаемый декоратором, уже имеет атрибут __doc__, в котором перечислены строки документов всех декорированных функций. Из документации, которую вы связали:

Dispatcher автоматически создает подробную строку документации. Чтобы добавить описание самого мультиметода, укажите его при создании файла Dispatcher.

Строка документации есть и для вашего примера:

>>> print(my_add.__doc__)
Multiply dispatched method: my_add

Inputs: <str, str>
-------------------
A flavor of 'add' where two strings are concatenated.

Inputs: <int, int>
-------------------
A flavor of 'my_add' where two strings are concatenated.

(обратите внимание, что строки документации правильно отражены в вашем примере с ошибками).

Настоящая проблема заключается в том, что help() может печатать только строку __doc__ класса class, поэтому print(type(my_add).__doc__)) используется в качестве отправной точки. Это не то, что можно легко изменить; просто придерживайтесь прямой печати атрибута __doc__ экземпляра.

person Martijn Pieters    schedule 19.01.2018
comment
Ух ты! Я не думал, что решение будет таким простым! Большое спасибо, Мартейн! Интересно, что .__doc__ можно изменить, но help не может легко указать на это. Я думаю, понимаю, что вы говорите о классе. Но я не совсем понимаю, как это работает. Я всегда думал, что my_add.__doc__ - это точно то же самое, что и help(my_add), просто синтаксический сахар. Я предполагаю, что вы говорите, что my_add.__doc__ можно перезаписать, чтобы он указывал на функцию my_add, но help(my_add) по-прежнему фактически указывает на help(dispatch) и не может быть изменен. Это правильно? - person Mike Williamson; 19.01.2018
comment
@MikeWilliamson: Что будет производить help(1) или help('some string')? help() дает вам информацию о том, что вы можете делать с типом аргумента. Так что да, help(my_add) поможет вам по классу Dispatch, а не по конкретному экземпляру этого класса. В более широком смысле это означает, что __doc__ экземпляра не используется. - person Martijn Pieters; 19.01.2018