What should I do with the docstring of a decorated function if the decorator changes the API?

StackOverflow https://stackoverflow.com/questions/13938230

Pergunta

Background

I have a library with several functions that read from or write to files. Each function accepts the file as the first argument, either as a file object or a file name. Hence, all of the functions have the same piece of code at the beginning, similar to the following:

if isinstance(f, str):
    file_obj = open(f, 'w')
else:
    file_obj = f

Now I figured I could write this once in a decorator and wrap all the functions in it, and not repeat myself. (I'm also thinking of implementing the context manager interface within the same decorator.)

So if I do this, the functions will look like:

@file_aware('w')
def function(f, *args, **kwargs):
    """Do stuff. `f` can be file object or file name"""
    for line in f:
       ....

The question

My concern is that now the docstring of the function doesn't correspond to the code below it. (I'm planning to keep the docstring of the decorated function with functools.wraps.) Does it reduce the readability/maintainability/transparency of the code? From what I understand, decorators can easily come and go, but at the same time this one kinda changes the API (I'm not planning to remove the functionality). What's the "best practice" way for this case?

I can think of processing the docstring automatically inside the decorator, but:

  • that's not the most natural thing to do;
  • that only makes sense for online documentation, and won't help (rather, the opposite) someone reading the source code.
Foi útil?

Solução

One option would be to pass the docstring into the decorator. That way you would still have a docstring by the function definition for reading source code but if you were to change or remove the decorator you wouldn't end up with an incorrect docstring.

For example:

@file_aware(docstring="Do stuff. `f` can be file object or file name", mode="r")
def function(f, *args, **kwargs):
    for line in f:
       ....

Your file_aware decorator might then look something like this:

def file_aware(docstring, mode):
    def deco(func):
        @functools.wraps(func)
        def wrapped(f, *args, **kwargs):
            if isinstance(f, str):
                file_obj = open(f, mode)
            else:
                file_obj = f
            return func(file_obj, *args, **kwargs)
        wrapped.__doc__ = docstring
        return wrapped
    return deco
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top