Signature-changing decorator: properly documenting

2019-07-01 21:57发布

问题:

Let's say I have a custom decorator, and I want it to properly handle docstring of decorated function. Problem is: my decorator adds an argument.

from functools import wraps

def custom_decorator(f):
    @wraps(f)
    def wrapper(arg, need_to_do_more):
        '''
        :param need_to_do_more: if True: do more
        '''
        args = do_something(arg)

        if need_to_do_more:
            args = do_more(args)

        return f(args)

    return wrapper

You can see the argument is not actually passed to decorated function, but used by the wrapper - which may or may not be relevant here.

How can I properly handle documenting the additional argument? Is it a good practice for a wrapper to take an additional argument, or should I avoid it?

Or should I rather use a different solution, like:

  • making wrapper a simple higher order function, with the function it calls passed as the third argument
  • refactoring the wrapper into two separate functions?

回答1:

If the result of the decoration is expected to always supply the same thing to that argument, I'd recommend making it a parameterized decorator. I'm guessing you thought of that, but it needed to be said.

Other than that, I would definitely recommend breaking it in two, like your second suggestion. Then the user of the decorators can provide "overloaded" versions (not truly overloaded, since they will need different names) that use the two different decorators.

Another possible option is giving the parameter a default value.

Lastly, if you simply must keep it the way that it is, you need to append the new parameter documentation to the end of __doc__ after the wrapper definition.

So your example (shortened) would look like this:

def custom_decorator(f):
    @wraps(f)
    def wrapper(arg, need_to_do_more):
        ...

    wrapper.__doc__ += "/n:param need_to_do_more: if True: do more"
    return wrapper

This is because the @wraps(f) decorator replaces wrapper's documentation with f's. Adding it afterwards will actually combine the two.

The other option along these lines is to document custom_decorator so that it says that wrapped methods need to add the parameter to their documentation. This, as well as the splitting up of the decorator, put the burden on the user, but they make the intention more explicit ("...Explicit is better than implicit..." - Zen of Python)