argparse -- requiring either 2 values or none for an optional argument

14.7k views Asked by At

I'm trying to make an optional argument for a script that can either take no values or 2 values, nothing else. Can you accomplish this using argparse?

# desired output:
# ./script.py -a --> works
# ./script.py -a val1 --> error
# ./script.py -a val1 val2 --> works


version 1 -- accepts 0 or 1 values:

parser = argparse.ArgumentParser()
parser.add_argument("-a", "--action", nargs="?", const=True, action="store", help="do some action")
args = parser.parse_args()

# output:
# ./script.py -a --> works
# ./script.py -a val1 --> works
# ./script.py -a val1 val2 --> error


version 2 - accepts exactly 2 values:

parser = argparse.ArgumentParser()
parser.add_argument("-a", "--action", nargs=2, action="store", help="do some action")
args = parser.parse_args()

# output:
# ./script.py -a --> error
# ./script.py -a val1 --> error
# ./script.py -a val1 val2 --> works


How do you combine these 2 different versions so that the script accepts 0 or 2 values for the argument, but rejects it when it only has 1 value?

4

There are 4 answers

9
Martijn Pieters On BEST ANSWER

You'll have to do your own error checking here. Accept 0 or more value, and reject anything other than 0 or 2:

parser = argparse.ArgumentParser()
parser.add_argument("-a", "--action", nargs='*', action="store", help="do some action")
args = parser.parse_args()

if args.action is not None and len(args.action) not in (0, 2):
    parser.error('Either give no values for action, or two, not {}.'.format(len(args.action)))

Note that args.action is set to None when no -a switch was used:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument("-a", "--action", nargs='*', action="store", help="do some action")
_StoreAction(option_strings=['-a', '--action'], dest='action', nargs='*', const=None, default=None, type=None, choices=None, help='do some action', metavar=None)
>>> args = parser.parse_args([])
>>> args.action is None
True
>>> args = parser.parse_args(['-a'])
>>> args.action
[]
4
poke On

Just handle that case yourself:

parser.add_argument("-a", "--action", nargs='*', action="store", help="do some action")
args = parser.parse_args()

if args.action is not None:
    if len(args.action) not in (0, 2):
        parser.error('Specify no or two actions')

    # action was specified but either there were two actions or no action
else:
    # action was not specified

Of course you should update the help text in that case so that the user has a chance to know this before running into the error.

2
cr1msonB1ade On

How about the required argument:parser.add_argument("-a", "--action", nargs=2, action="store", help="do some action", required=False)

0
chepner On

Have your option take a single, optional comma-separated string. You'll use a custom type to convert that string to a list and verify that it has exactly two items.

def pair(value):
    rv = value.split(',')
    if len(rv) != 2:
        raise argparse.ArgumentParser()
    return rv

parser.add_argument("-a", "--action", nargs='?',
                    type=pair, metavar='val1,val2',
                    help="do some action")
print parser.parse_args()

Then you'd use it like

$ ./script.py -a
Namespace(action=None)
$ ./script.py -a val1,val2
Namespace(action=['val1','val2'])
$ ./script.py -a val1
usage: tmp.py [-h] [-a [ACTION]]
script.py: error: argument -a/--action: invalid pair value: 'val1'
$ ./script.py -a val1,val2,val3
usage: tmp.py [-h] [-a [ACTION]]
script.py: error: argument -a/--action: invalid pair value: 'val1,val2,val3'

You can adjust the definition of pair to use a different separator and to return something other than a list (a tuple, for example).

The metavar provides a better indication that the argument to action is a pair of values, rather than just one.

$ ./script.py -h
usage: script.py [-h] [-a [val1,val2]]

optional arguments:
  -h, --help            show this help message and exit
  -a [val1,val2], --action [val1,val2]
                        do some action