Search code examples
pythonoperator-overloadingpython-decoratorsdispatch

How to document Python functions with overload/dispatch decorators?


Applying Documentation to Multi-Dispatched Functions

I am using the multipledispatch package, in a fashion similar to the example code below. I want to be able to see the docstring text when I ask for help(my_add) in the Python command line, but instead all I see is information about the decorator.

Functools.wraps must be the way to do it, but how?

I have looked up functools.wraps, which I'm sure is what I want to use. I have found examples of how to use it, such as this and this.

But I still don't entirely understand two issues:

  1. How to apply the functools.wraps to external decorators that I don't "own".
  2. How to apply it specifically to this case with multiple dispatch, since the function I want to wrap will have multiple docstrings associated with the same function name.

Example: Decorated Function Creation

Below is an example to help explain.

>>> 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)

(Traceback details removed...)

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

I wanted to show that error and the different dispatches just to show that that part is working as I want it (looking for the matched dispatch and calling the associated "flavor" of the function).

Example: Calling help on the Decorated Function Fails!

But next, if I try to look at the help, instead of seeing the simple docstring that I have provided, I see the docstring associated with the @dispatch decorator.

>>> 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)
 |  

etc.


I'm not even sure what it should show, since there are potentially 2 conflicting docstrings I'd like to push forward. So, I tried to see if I could call help on a function that's actually run, but then of course it gives me help on the returned data type. E.g.,

>>> 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

Solution

  • The functools.wraps() decorator would have to be part of the decorator implementation and you can't apply it after the fact. The issue here is that the decorator returns an instance of a custom class, and help() can only show documentation for the class of that instance.

    The Dispatcher() instance the decorator returns already has a __doc__ attribute, one which lists the doc strings of all decorated functions. From the documentation you linked:

    The Dispatcher creates a detailed docstring automatically. To add a description of the multimethod itself, provide it when creating the Dispatcher.

    The docstring is there for your example too:

    >>> 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.
    

    (note that the docstrings are reflected correctly from your example, complete with errors).

    The real issue here is that help() can only print the __doc__ string of the class, so print(type(my_add).__doc__)) is used as a starting point. This is not something that can easily be changed; just stick to printing the __doc__ attribute of the instance directly.