368

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
2
  • 5
    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 Commented Jun 12, 2014 at 9:15
  • 13
    @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.
    – Asclepius
    Commented Oct 5, 2016 at 6:13

7 Answers 7

493

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
6
  • 1
    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 Commented Apr 13, 2019 at 19:45
  • 2
    @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
    – Devin
    Commented Jun 12, 2019 at 13:50
  • @poke: Nice solution! But this doesn't help in case you need mutual exclusive groups, or am I missing anything?
    – Judge
    Commented Jan 16, 2020 at 15:40
  • @Judge i would recommend reading this pymotw.com/3/argparse/#mutually-exclusive-options Commented Apr 23, 2020 at 13:16
  • 1
    @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.
    – poke
    Commented Jul 9, 2020 at 22:39
136

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.

5
  • 3
    Does this actually force argparse to treat any of the arguments as required?
    – Anthony
    Commented Oct 12, 2017 at 19:41
  • 7
    I think the 'required' argument still needs to be set when adding an argument. Commented Oct 13, 2017 at 15:13
  • 8
    @Anthony - no you need the 'required=True' in add_argument for that. The above answer just illustrates argument grouping. Commented Feb 28, 2018 at 16:10
  • 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?
    – Isi
    Commented Sep 30, 2020 at 10:20
  • 2
    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
    Commented Apr 7, 2022 at 21:56
67

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
3
  • 1
    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.
    – machin
    Commented Apr 22, 2017 at 9:21
  • This is great. Solves the --help item showing up in a second optional list.
    – Jeremy
    Commented Nov 18, 2017 at 19:17
  • 3
    Note: this answer breaks the exposed API, check answer by Bryan_D down below.
    – lol
    Commented May 2, 2020 at 13:26
49

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
3
  • 1
    Can you explain how RalphyZ's answer breaks the exposed API? Commented Sep 19, 2019 at 15:46
  • 15
    _action_groups is intended for internal use only. Therefore, there is no compatibility guarantee across versions.
    – Bryan_D
    Commented Sep 20, 2019 at 19:47
  • But then, [-h] is listed after positional arguments in usage message
    – Kiruahxh
    Commented May 2, 2023 at 13:46
5

You don't need to override the optional group.

Just do:

parser = argparse.ArgumentParser()
required = parser.add_argument_group('required arguments')
required.add_argument('--required_arg', required=True)
# All arguments set via parser directly will automatically go to the optional group
parser.add_argument('--optional_arg')
parser.print_help()

will print out

usage: [-h] --required_arg REQUIRED_ARG [--optional_arg OPTIONAL_ARG]

optional arguments:
  -h, --help            show this help message and exit
  --optional_arg OPTIONAL_ARG

required arguments:
  --required_arg REQUIRED_ARG

If you wish to have required arguments before optional, you can do the following:

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

that will print groups in the correct order:

usage: [-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
2
  • 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
    Commented Apr 7, 2022 at 21:49
  • make that @poke's answer - all (useful) answers except his use _action_groups.
    – nclark
    Commented Apr 7, 2022 at 21:58
5

NB: per Christophe Vu-Brugier, the code below does not work starting from Python version 3.10.

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
1
  • 1
    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. Commented Jun 28, 2022 at 9:33
0

The required arguments are usually "positional" arguments when using argparse.

    parser = argparse.ArgumentParser(description="Foo")
    parser.add_argument("username")
    parser.add_argument("password")

This will add two positional arguments which are required.

Not the answer you're looking for? Browse other questions tagged or ask your own question.