Argparse: Required arguments listed under "optional arguments"?

260,557

Solution 1

Parameters starting with - or -- are usually considered optional. All other parameters are positional parameters and as such required by design (like positional function arguments). It is possible to require optional arguments, but this is a bit against their design. Since they are still part of the non-positional arguments, they will still be listed under the confusing header “optional arguments” even if they are required. The missing square brackets in the usage part however show that they are indeed required.

See also the documentation:

In general, the argparse module assumes that flags like -f and --bar indicate optional arguments, which can always be omitted at the command line.

Note: Required options are generally considered bad form because users expect options to be optional, and thus they should be avoided when possible.

That being said, the headers “positional arguments” and “optional arguments” in the help are generated by two argument groups in which the arguments are automatically separated into. Now, you could “hack into it” and change the name of the optional ones, but a far more elegant solution would be to create another group for “required named arguments” (or whatever you want to call them):

parser = argparse.ArgumentParser(description='Foo')
parser.add_argument('-o', '--output', help='Output file name', default='stdout')
requiredNamed = parser.add_argument_group('required named arguments')
requiredNamed.add_argument('-i', '--input', help='Input file name', required=True)
parser.parse_args(['-h'])
usage: [-h] [-o OUTPUT] -i INPUT
Foo
optional arguments:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Output file name
required named arguments:
  -i INPUT, --input INPUT
                        Input file name

Solution 2

Since I prefer to list required arguments before optional, I hack around it via:

parser = argparse.ArgumentParser()
parser._action_groups.pop()
required = parser.add_argument_group('required arguments')
optional = parser.add_argument_group('optional arguments')
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')
return parser.parse_args()

and this outputs:

usage: main.py [-h] --required_arg REQUIRED_ARG [--optional_arg OPTIONAL_ARG]
required arguments:
  --required_arg REQUIRED_ARG
optional arguments:
  --optional_arg OPTIONAL_ARG

I can live without -h, --help showing up in the optional arguments group.

Solution 3

Building off of @Karl Rosaen

parser = argparse.ArgumentParser()
optional = parser._action_groups.pop() # Edited this line
required = parser.add_argument_group('required arguments')
# remove this line: optional = parser...
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')
parser._action_groups.append(optional) # added this line
return parser.parse_args()

and this outputs:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]
required arguments:
  --required_arg REQUIRED_ARG
optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG

Solution 4

One more time, building off of @RalphyZ

This one doesn't break the exposed API.

from argparse import ArgumentParser, SUPPRESS
# Disable default help
parser = ArgumentParser(add_help=False)
required = parser.add_argument_group('required arguments')
optional = parser.add_argument_group('optional arguments')
# Add back help 
optional.add_argument(
    '-h',
    '--help',
    action='help',
    default=SUPPRESS,
    help='show this help message and exit'
)
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')

Which will show the same as above and should survive future versions:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]
required arguments:
  --required_arg REQUIRED_ARG
optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG

Solution 5

by default there're 2 argument groups in parser._action_groups: positional arguments and named arguments (titled 'optional arguments'). you can add your named optional arguments to the existing 'optional arguments' group, and required named arguments to a new 'required arguments' group. After that you can re-order groups:

import argparse
parser = argparse.ArgumentParser(description='Foo')
required = parser.add_argument_group('required arguments')
required.add_argument('-i','--input', help='Input file name', required=True)
parser.add_argument('-o','--output', help='Output file name', default="stdout")
groups_order = {
    'positional arguments': 0,
    'required arguments': 1,
    'optional arguments': 2
}
parser._action_groups.sort(key=lambda g: groups_order[g.title])
parser.parse_args(['-h'])

output:

usage: argparse_argument_groups.py [-h] -i INPUT [-o OUTPUT]
Foo
required arguments:
  -i INPUT, --input INPUT
                        Input file name
optional arguments:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Output file name
Share:
260,557

Related videos on Youtube

mort
Author by

mort

Updated on February 08, 2022

Comments

  • mort
    mort 9 months

    I use the following simple code to parse some arguments; note that one of them is required. Unfortunately, when the user runs the script without providing the argument, the displayed usage/help text does not indicate that there is a non-optional argument, which I find very confusing. How can I get python to indicate that an argument is not optional?

    Here is the code:

    import argparse
    if __name__ == '__main__':
        parser = argparse.ArgumentParser(
            description='Foo')
        parser.add_argument('-i','--input', help='Input file name', required=True)
        parser.add_argument('-o','--output', help='Output file name', default="stdout")
        args = parser.parse_args()
        print ("Input file: %s" % args.input )
        print ("Output file: %s" % args.output )
    

    When running above code without providing the required argument, I get the following output:

    usage: foo.py [-h] -i INPUT [-o OUTPUT]
    Foo
    optional arguments:
        -h, --help            show this help message and exit
        -i INPUT, --input INPUT
                              Input file name
        -o OUTPUT, --output OUTPUT
                              Output file name
    
    • Jaime Rodríguez-Guerra over 8 years
      In the usage line, the -i INPUT part is not surrounded by square brackets, which subtlety indicates that is indeed, required. Also, you can manually explain that through the help param
    • Asclepius
      Asclepius about 6 years
      @JaimeRGP Yes, but that's not sufficient, of course, and it's also less than prominent. The assigned group name optional arguments for the required arguments is still misleading.
  • machin over 5 years
    BTW, are there any ways (methods) how to get access to _action_group without accessing protected member? In my case I need to add some argument to already existent (custom) group.
  • Anthony
    Anthony about 5 years
    Does this actually force argparse to treat any of the arguments as required?
  • Karl Rosaen about 5 years
    I think the 'required' argument still needs to be set when adding an argument.
  • Jeremy almost 5 years
    This is great. Solves the --help item showing up in a second optional list.
  • Paul Cezanne
    Paul Cezanne almost 5 years
    That's really nice.
  • user2275693 over 4 years
    @Anthony - no you need the 'required=True' in add_argument for that. The above answer just illustrates argument grouping.
  • Zarar Mahmud
    Zarar Mahmud over 3 years
    I have been having the same issue. I tried you solution. It does add the arguments to the new group but my code doesn't seem to work after that. Any solutions would be appreciated. Link to my code - pastebin.com/PvC2aujz
  • Devin over 3 years
    @ZararMahmud: You are passing in empty arguments in line 24 of your code: parser.parse_args([]) Instead, use parser.parse_args() with no arguments to capture the contents of sys.argv. Per argparse
  • jeremysprofile
    jeremysprofile about 3 years
    Can you explain how RalphyZ's answer breaks the exposed API?
  • Bryan_D about 3 years
    _action_groups is intended for internal use only. Therefore, there is no compatibility guarantee across versions.
  • Judge
    Judge almost 3 years
    @poke: Nice solution! But this doesn't help in case you need mutual exclusive groups, or am I missing anything?
  • Peter Moore
    Peter Moore over 2 years
    @Judge i would recommend reading this pymotw.com/3/argparse/#mutually-exclusive-options
  • lol
    lol over 2 years
    Note: this answer breaks the exposed API, check answer by Bryan_D down below.
  • mrgloom
    mrgloom over 2 years
    What is the difference between - and --?
  • poke
    poke over 2 years
    @mrgloom Options are usually defined with -- and a precise name to explain what they are for. A single - can then be used to define a short alias for commonly used options. In my example, you could use both --output out.txt or -o out.txt for the exact same thing. The -o is just a short alias for --output. Some command line tools additionally allow you to chain these short aliases. E.g. tar -xf archive.tar is short for tar --extract --file=archive.tar.
  • Isi
    Isi about 2 years
    Now in the first line it looks as if the argument would be optional, so opposite of what OP shows. Any chance that required arguments are shown before optional ones AND have no brackets?
  • nclark
    nclark 7 months
    You're using a protected member _action_groups which puts you at serious risk of breaking on a minor release upgrade. I'll stick with @Karl Rosaen's answer.
  • nclark
    nclark 7 months
    As noted in other answers, the use of private member _action_groups violates the API contract and risks breaking on the next minor release.
  • nclark
    nclark 7 months
    make that @poke's answer - all (useful) answers except his use _action_groups.
  • Christophe Vu-Brugier 5 months
    I used your snippet for a long time and it served me well. Thank you for that. However, I observe it no longer works with Python 3.10 because "optional arguments" was renamed to "options". Running this code triggers a "KeyError: options" exception on Python 3.10. Consequently, I decided it was safer for my project to drop the distinction between "required" and "optional" arguments.