python/cpython · error · ArgumentError

invalid choice: %(value)r (choose from %(choices)s)

Error message

invalid choice: %(value)r (choose from %(choices)s)

What it means

After type conversion, _check_value verifies the result against action.choices; if the value is not found, argparse raises ArgumentError listing the value and the full set of allowed choices (each str()-ed and repr'd). Choices can be any container; a string choices value is treated as an iterable of characters, which frequently surprises users.

Solutions

  1. Use one of the listed choices exactly (watch case and whitespace).
  2. Normalize input with type=str.lower and list choices in lowercase.
  3. Fix the declaration: choices must be a sequence of allowed values, e.g. choices=['a','b'], not choices='ab'.
  4. Enable suggest_on_error (Python 3.14+) on the parser to get 'maybe you meant' hints for near-misses.

Example fix

# before
parser.add_argument('--fmt', choices=['json', 'xml'])
parser.parse_args(['--fmt', 'JSON'])  # invalid choice: 'JSON'

# after
parser.add_argument('--fmt', choices=['json', 'xml'], type=str.lower)
Defensive patterns

Strategy: validation

Validate before calling

def validate_choices(parser, argv):
    by_name = {s: a for a in parser._actions for s in a.option_strings}
    for i, tok in enumerate(argv):
        name, sep, inline = tok.partition('=')
        if name in by_name and by_name[name].choices:
            val = inline if sep else (argv[i + 1] if i + 1 < len(argv) else None)
            if val is not None and val not in map(str, by_name[name].choices):
                return (name, val)
    return None

Try / catch

try:
    args = parser.parse_args(argv)
except argparse.ArgumentError as e:
    if 'invalid choice' in str(e):
        allowed = re.search(r'choose from (.*)\)', str(e))
        print(f'allowed values: {allowed.group(1) if allowed else "see --help"}')

Prevention

When it happens

Trigger: add_argument('--fmt', choices=['json','xml']) invoked with --fmt=yaml; a numeric choices range like list(range(1,11)) with input 12; choices='abc' accidentally passing a string, so only single characters 'a','b','c' are accepted.

Common situations: Typos in enum-like flags; case sensitivity ('JSON' vs 'json'); passing a string instead of a list of strings as choices; version-gated choices where a new value is rejected by an older tool build.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/861f310b0daa2355. Report an issue: GitHub.

Appendix: source

Thrown at Lib/argparse.py:2838

            return

        if isinstance(choices, str):
            choices = iter(choices)

        if value not in choices:
            args = {'value': str(value),
                    'choices': ', '.join(repr(str(choice)) for choice in action.choices)}
            msg = _('invalid choice: %(value)r (choose from %(choices)s)')

            if self.suggest_on_error and isinstance(value, str):
                if all(isinstance(choice, str) for choice in action.choices):
                    suggestions = difflib.get_close_matches(value, action.choices, 1)
                    if suggestions:
                        args['closest'] = suggestions[0]
                        msg = _('invalid choice: %(value)r, maybe you meant %(closest)r? '
                                '(choose from %(choices)s)')

            raise ArgumentError(action, msg % args)

    # =======================
    # Help-formatting methods
    # =======================

    def format_usage(self, formatter=None):
        if formatter is None:
            formatter = self._get_formatter()
        formatter.add_usage(self.usage, self._actions,
                            self._mutually_exclusive_groups)
        return formatter.format_help()

    def format_help(self, formatter=None):
        if formatter is None:
            formatter = self._get_formatter()

        # usage
        formatter.add_usage(self.usage, self._actions,

View on GitHub (pinned to bc6749cc3b)