2

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?

3
  • 1
    Take a look at the docs.python.org/3/library/inspect.html module Commented Feb 19, 2019 at 21:35
  • 1
    See docopt.readthedocs.io/en/0.2.0 Commented Feb 19, 2019 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`). Commented Feb 19, 2019 at 22:20

1 Answer 1

2

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)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.