I have a collection of functions with (mostly) shared parameters but different processes. I'd like to use a decorator to add the description for each parameter to a function's headline-level docstring.
I've tried to mimic the structure found in this answer by incorporating a nested function within appender
but failed. I've also tried functools.partial
but something is slightly off.
My attempt:
def appender(func, *args):
"""Appends additional parameter descriptions to func's __doc__."""
def _doc(func):
params = ''.join([defaultdocs[arg] for arg in args])
func.__doc__ += '\n' + params
return func
return _doc
defaultdocs = {
'a' :
"""
a : int, default 0
the first parameter
""",
'b' :
"""
b : int, default 1
the second parameter
"""
}
@appender('a')
def f(a):
"""Title-level docstring."""
return a
@appender('a', 'b')
def g(a, b):
"""Title-level docstring."""
return a + b
This fails, and it fails I believe because the first arg passed to appender
is interpreted as func
. So when I view the resulting docstring for g
I get:
print(g.__doc__)
Title-level docstring.
b : int, default 1
the second parameter
because, again, 'a'
is interpreted to be 'func'
when I want it to be the first element of *args
. How can I correct this?
Desired result:
print(g.__doc__)
Title-level docstring.
a : int, default 0
the first parameter
b : int, default 1
the second parameter
This happens because the variable names you pass actually get captured into a func
argument.
In order to do callable decorators in Python you need to code the function twice, having external function to accept decorator arguments and internal function to accept original function. Callable decorators are just higher-order functions that return other decorators. For example:
def appender(*args): # This is called when a decorator is called,
# e. g. @appender('a', 'b')
"""Appends additional parameter descriptions to func's __doc__."""
def _doc(func): # This is called when the function is about
# to be decorated
params = ''.join([defaultdocs[arg] for arg in args])
func.__doc__ += '\n' + params
return func
return _doc
The external (appender
) function acts as a factory for new decorator while _doc
function is an actual decorator. Always pass it this way:
Once the Python sees this:
@appender('a', 'b')
def foo(): pass
...it will do something like this under the hood:
foo = appender('a', 'b')(foo)
...which expands to this:
decorator = appender('a', 'b')
foo = decorator(foo)
Because of how scopes in Python work, each newly returned _doc
function instance will have its own local args
value from the external function.