Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Argparse and ArgumentDefaultsHelpFormatter. Formatting of default values when sys.stdin/stdout are selected as default

I am interested in using the ArgumentDefaultsHelpFormatter class formatter of argparse (my program has several sub-commands). By default, the input and output arguments are set to sys.stdin and sys.stdout respectively. However, the formatting for these two arguments may be a little bit confusing for users (e.g (default: ', mode 'r' at 0x10028e0c0>). Is there a way to specifically and easily change the output format for these two arguments to get something like 'default: STDIN' or 'default: STDOUT'?

Thank you

import sys
import argparse

parser = argparse.ArgumentParser(prog='PROG', 
                                 formatter_class=argparse.ArgumentDefaultsHelpFormatter)

parser.add_argument('--infile',
                '-i',
                metavar='File',
                help='The input file/stream.',
                default=sys.stdin,
                type=argparse.FileType('r'),
                required=False)

parser.add_argument('--outfile',
                '-o',
                metavar='File',
                help='The output file/stream.',
                default=sys.stdout,
                type=argparse.FileType('r'),
                required=False)

parser.add_argument('--whatever-arg',
                '-w',
                type=str,
                default='any',
                help='Change something',
                required=False)


args = parser.parse_args()
parser.print_help()

Which gives:

usage: PROG [-h] [--infile File] [--outfile File]
            [--whatever-arg WHATEVER_ARG]

optional arguments:
  -h, --help            show this help message and exit
  --infile File, -i File
                        The input file/stream. (default: <open file '<stdin>',
                        mode 'r' at 0x10028e0c0>)
  --outfile File, -o File
                        The output file/stream. (default: <open file
                        '<stdout>', mode 'w' at 0x10028e150>)
  --whatever-arg WHATEVER_ARG, -w WHATEVER_ARG
                        Change something (default: any)
like image 521
dputhier Avatar asked Dec 31 '15 10:12

dputhier


2 Answers

You can subclass ArgumentDefaultsHelpFormatter to do what you want.

from argparse import ArgumentDefaultsHelpFormatter,RawDescriptionHelpFormatter

class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
    def _get_help_string(self, action):
        help = action.help
        if '%(default)' not in action.help:
            if action.default is not argparse.SUPPRESS:
                defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]
                if action.option_strings or action.nargs in defaulting_nargs:
                    if type(action.default) == type(sys.stdin):
                        print action.default.name
                        help += ' (default: ' + str(action.default.name) + ')'
                    else:
                        help += ' (default: %(default)s)'
        return help

parser = argparse.ArgumentParser(prog='PROG', formatter_class=CustomFormatter)

result for me is:

optional arguments:
  -h, --help            show this help message and exit
  --infile File, -i File
                        The input file/stream. (default: <stdin>)
  --outfile File, -o File
                        The output file/stream. (default: <stdout>)
  --whatever-arg WHATEVER_ARG, -w WHATEVER_ARG
                        Change something (default: any)
like image 139
A.H Avatar answered Nov 06 '22 06:11

A.H


If you give default='-' instead of sys.stdin, the help display would be

the input file/stream. (default: -)

That is, help shows the default string, but FileType converts '-' to stdin/out.

As A.H shows you can customize the _get_help_string method. It doesn't matter which class you inherit from, since modifying that method is all that ADHF does:

class ArgumentDefaultsHelpFormatter(HelpFormatter):
    """...
    """
    def _get_help_string(self, action):
        help = action.help
        if '%(default)' not in action.help:
            if action.default is not SUPPRESS:
                defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
                if action.option_strings or action.nargs in defaulting_nargs:
                    help += ' (default: %(default)s)'
        return help

And note that all this modification does is add a string to the help parameter - just the (default: %(default)s)

That means you could get a similar effect by adjusting your own help lines, e.g.

parser.add_argument('--infile',
                '-i',
                metavar='File',
                help='The input file/stream, (default: stdin).',
                default='-',
                type=argparse.FileType('r'))

parser.add_argument('--whatever-arg',
                '-w',
                default='any',
                help='Change something, (default: %(default)s)')

In other words it saves you typing (default: %(default)s) for 28 of your arguments.

If you are uncomfortable customizing the HelpFormatter class (though that is what developers recommend - with suitable cautions), you could tweak your own setup. For example, make simple helper function that adds the extra string to each help line:

def foohelp(astr):
    return astr + ' (default: %(default)s)'

arg1 = parser.add_argument('-f','--fooarg', help=foohelp('help string'))

Speaking of changing the setup programmatically, it's worth noting that add_argument creates an Action object. You can save a link to that, as I did here, and tweak parameters.

arg1 = parser.add_argument('-f','--fooarg', help='help string')
print arg1.help
arg1.help = foohelp(arg1.help)  # modify help after creation
print arg1.help

With 30 arguments you probably have done a lot of copy-n-paste to define them, or writen various helper functions to streamline the setup. Adding the default display is just another of those tasks. You can do it during setup, or you can do it via a custom Formatter.

like image 37
hpaulj Avatar answered Nov 06 '22 05:11

hpaulj