Search code examples
pythonargparse

Python argparse: type inconsistencies when combining 'choices', 'nargs' and 'default'


I have the following python program:

#!/usr/bin/env python

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('arg', choices=['foo', 'bar', 'baz'], default='foo', nargs='*')

args = parser.parse_args()

print(args)

If I invoke the program like this:

./prog.py

the output is

Namespace(arg='foo')

But if I invoke the program with foo as an argument:

./prog.py foo

the output is

Namespace(arg=['foo'])

Question

How can I get arg's default value to become a list?

I've tried

I've tried setting default=['foo'] but that results in:

prog.py: error: argument arg: invalid choice: ['foo'] (choose from 'foo', 'bar', 'baz')

Solution

  • This is a duplicate of an old, but open, bug/issue

    http://bugs.python.org/issue9625 (argparse: Problem with defaults for variable nargs when using choices)

    A positional with * gets some special handling. Its default is always passed through the choices test if you don't provide values.

    Compare that with the case of an optional

    In [138]: p=argparse.ArgumentParser()
    In [139]: a=p.add_argument('--arg',choices=['foo','bar','baz'],nargs='*')
    
    In [140]: p.parse_args([])
    Out[140]: Namespace(arg=None)
    In [141]: a.default=['foo']
    In [142]: p.parse_args([])
    Out[142]: Namespace(arg=['foo'])
    

    The default is accepted without testing:

    In [143]: a.default=['xxx']
    In [144]: p.parse_args([])
    Out[144]: Namespace(arg=['xxx'])
    

    The relevant code is:

    def _get_values(self, action, arg_strings):
        ...
        # when nargs='*' on a positional, if there were no command-line
        # args, use the default if it is anything other than None
        elif (not arg_strings and action.nargs == ZERO_OR_MORE and
              not action.option_strings):
            if action.default is not None:
                value = action.default
            else:
                value = arg_strings
            self._check_value(action, value)
    

    The proposed bug/issue patch makes a small change to this block of code.