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.

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)

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.