7

In argparse, how can I create an optional positional command line option that takes multiple arguments which all need to be part of a list of choices?

In the following example, I want to allow any subset of the list ['a', 'b', 'c'] – i.e. ['a'], ['a', 'c'], ... and, crucially, the empty list []. I was expecting the following to achieve that, but it fails if the argument is omitted.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('letters', nargs='*', choices=['a', 'b', 'c'])
args = parser.parse_args()

error: argument letters: invalid choice: [] (choose from 'a', 'b', 'c')

UPDATE: I have since found that choices=['a', 'b', 'c', []] appears to do the job. That strikes me as inconsistent as it suggests that argparse checks for [] in choices which would then imply that ['a'] should be in choices as well (rather than 'a').

jhansen
  • 1,096
  • 1
  • 8
  • 17
  • 1
    Any object that support the `in` operator can be passed to choices. It is odd though that you need to use an empty list rather than an empty string or `None` to accomplish an empty choice. – James Aug 31 '19 at 15:47
  • Agreed. `None` and `''` were the first things I tried when I got the error. Although I was expecting the check to be along the lines of `all([_ in choices for _ in args])`, which shouldn't require the explicit inclusion in choices. – jhansen Aug 31 '19 at 15:55
  • The `*` positional is satisfied with a `[]` empty list of inputs. But it shouldn't be passing it through the choices test. I'll have to look at the code to see what's happening. It would be in the `_get_values` and `_get_value` methods. I'm surprised I don't recall seeing a bug/issue about this. – hpaulj Aug 31 '19 at 16:02
  • 3
    A related issue just got more attention: https://bugs.python.org/issue9625, Other links https://bugs.python.org/issue27227, https://bugs.python.org/issue16878, https://stackoverflow.com/questions/41750896/python-argparse-type-inconsistencies-when-combining-choices-nargs-and-def – hpaulj Aug 31 '19 at 16:19
  • Are repeats supposed to be allowed? I mean like `parser.parse_args(['a', 'a'])` doesn't raise an error. – wjandrea Dec 22 '19 at 18:27

1 Answers1

0

I think this question (asking for an empty list default) is basically a special case of the more general question, asking for other defaults.

To adapt my other answer based on typed_argparse to this specific case:

import argparse
from typed_argparse import Choices

parser = argparse.ArgumentParser()
parser.add_argument(
    "letters", 
    choices=Choices("a", "b", "c"),  # Wrap the choices in Choices
    default=[],  # Empty list possible as default
    nargs="*",
)

The implementation of Choices is quite simple, if you prefer something copy/pasteable.

bluenote10
  • 23,414
  • 14
  • 122
  • 178