Let's say we have a complex API function, imported from some library.
def complex_api_function(
number, <lots of positional arguments>,
<lots of keyword arguments>):
'''really long docstring'''
# lots of code
I want to write a simple wrapper around that function to make a tiny change. For example, it should be possible to pass the first argument as a string. How to document this? I considered the following options:
def my_complex_api_function(number_or_str, *args, **kwargs):
'''
Do something complex.
Like `complex_api_function`, but first argument can be a string.
Parameters
----------
number_or_str : int or float or str
Can be a number or a string that can be interpreted as a float.
<copy paste description from complex_api_function docstring>
*args
Positional arguments passed to `complex_api_function`.
**kwargs
Keyword arguments passed to `complex_api_function`.
Returns
-------
<copy paste from complex_api_function docstring>
Examples
--------
<example where first argument is a string, e.g. '-5.0'>
'''
return complex_api_function(float(number_or_str), *args, **kwargs)
Disadvantage: User has to look at the docs of complex_api_function
to get
information about *args
and **kwargs
. Needs adjustment when the copy pasted sections from complex_api_function
change.
Copy and paste complex_api_function
's signature (instead of using *args
and **kwargs
) and its docstring. Make a tiny change to the docstring that mentions that the first argument can also be a string. Add an example.
Disadvantage: verbose, has to be changed when complex_api_function
changes.
Decorate my_complex_api_function
with functools.wraps(complex_api_function)
.
Disadvantage: There's no information that number
can also be a string.
I'm looking for an answer that does not hinge on the details of what changes in my_complex_api_function
. The procedure should work for any tiny adjustment to the original complex_api_function
.
You could automate the "specialization" of the original docstring with an addendum. For instance, pydoc is using the special attribute __doc__
. You could write a decorator which automatically overrides the original function __doc__
with your addendum.
For instance:
def extend_docstring(original, addendum):
def callable(func):
func.__doc__ = original + addendum
return func
return callable
def complex_api_function(a, b, c):
'''
This is a very complex function.
Parameters
----------
a: int or float
This is the argument A.
b: ....
'''
print('do something')
@extend_docstring(
complex_api_function.__doc__,
'''
Addendum
--------
Parameter a can also be a string
'''
)
def my_complex_api_function(a, b, c):
return complex_api_function(float(a), b, c)
or...
def extend_docstring(original):
def callable(func):
func.__doc__ = original + func.__doc__
return func
return callable
def complex_api_function(a, b, c):
'''
This is a very complex function.
Parameters
----------
a: int or float
This is the argument A.
b: ....
'''
print('do something')
@extend_docstring(complex_api_function.__doc__)
def my_complex_api_function(a, b, c):
'''
Addendum
--------
Parameter a can also be a string
'''
return complex_api_function(float(a), b, c)
If you run pydoc (pydoc3 -w my_module.py
) it produces: preview of html generated by pydoc
Additional note: If you are using Python 3 you could use annotations to document the type(s) of your function parameters. It offers a lot of benefits, not only documentation. For instance:
from typing import Union
def my_complex_api_function(number_or_str: Union[int, float, str], *args, **kwargs):
I'd recommend something like the following:
def my_complex_api_function(number_or_str, *args, **kwargs):
"""This function is a light wrapper to `complex_api_function`.
It allows you to pass a string or a number, whereas `complex_api_function` requires a
number. See :ref:`complex_api_function` for more details.
:param number_or_str: number or str to convert to a number and pass to `complex_api_function`.
:param args: Arguments to pass to `complex_api_function`
:param kwargs: Keyword arguments to pass to `complex_api_function`
:return: Output of `complex_api_function`, called with passed parameters
"""
This is clear and concise. But please also remember that, if using a documentation system like sphinx, to link the functions with :ref:`bob`
or similar.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With