1

When writing a script, I sometimes use a function to encapsulate exactly what the script does. This is because I might want to call the function from code or run it as a script. Is there any way to avoid repeating the documentation of the function arguments in the help strings in argparse? For example:

import argparse
def my_function(arg_one, arg_two):
    """
    arg_one: The first argument.
    arg_two: The second argument.
    """
    print("The first argument is %r" % arg_one)
    print("The second argument is %r" % arg_two)

if __name__=="main":
    parser = argparse.ArgumentParser()
    parser.add_argument('--arg-one', help="The first argument.")
    parser.add_argument('--arg-two', help="The second argument.")
    args = parser.parse_args()
    my_function(args.arg_one, args.arg_two)

Since the arguments of the function and the script exactly correspond, you can see that I've had to document them twice ("The first argument", "The second argument"). Its a trivial problem, but its really annoying. Should I just not be using a function at all?

mchristos
  • 1,487
  • 1
  • 9
  • 24
  • 1
    Take a look at the https://docs.python.org/3/library/inspect.html module – Josep Valls Feb 19 '19 at 21:35
  • 1
    See https://docopt.readthedocs.io/en/0.2.0/ – liamhawkins Feb 19 '19 at 21:41
  • 1
    This is a good organization, despite the documentation redundancy. In practice you'll have multiple functions. Or maybe a `main` that can call various combinations of functions. And the parser might handle attributes that aren't used directly any function (e.g verbosity, logging). But there are various alternative parsers (or argparse frontends) that try to provide a more direct connection between functions and parser arguments (`plac, `docopt`). – hpaulj Feb 19 '19 at 22:20

1 Answers1

1

Here is how I would write it...

""" My Crazy Program
Usage:
    my_prog [options]

Options:
"""

def my_function(file_output, file_input, recursive=False):
    """
    output=str: specifies an output path
    input=str: specifies an input path
    recursive: apply recursively
    """
    print("The first argument is %r" % file_output)
    print("The second argument is %r" % file_input)
    print("The third argument is %r" % recursive)

# This is where the magic happens
__doc__ += '\n'.join(f'   --{parsed[0]: <15}  {parsed[1]}'
                     for parsed in (
                         line.strip().split(': ') 
                         for line in my_function.__doc__.split('\n'))
                     if len(parsed) == 2)

if __name__ == "__main__":
    from docopt import docopt
    ARGS = docopt(__doc__)
    my_function(ARGS['--output'], ARGS['--input'], ARGS['--recursive'])

Ok, you see the magic line (beginning by __doc__ += ...), it makes the documentation for the module which becomes:

 My Crazy Program
Usage:
    my_prog [options]

Options:
   --output=str       specifies an output path
   --input=str        specifies an input path
   --recursive        apply recursively

Then, docopt parses that and returns this dictionary:

$ python my_prog
{'--input': None,
 '--output': None,
 '--recursive': False}

$ python my_prog --output /dev/null --recursive
{'--input': None,
 '--output': '/dev/null',
 '--recursive': True}

Which can be used to call the function and get the result:

The first argument is '/dev/null'
The second argument is None
The third argument is True

I like this solution because it is single line but I will agree with you that it isn't beautiful, I will let you write your own beautiful function that does that automatically for every file :o)

Benoît P
  • 3,179
  • 13
  • 31