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
- Use one of the listed choices exactly (watch case and whitespace).
- Normalize input with type=str.lower and list choices in lowercase.
- Fix the declaration: choices must be a sequence of allowed values, e.g. choices=['a','b'], not choices='ab'.
- 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
- Normalize case with type=str.lower and list choices lowercase.
- Pass choices as a list of strings — a bare string becomes per-character choices.
- Echo the allowed set in your tool's docs so users never guess.
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
- invalid nargs value
- invalid choice: %(value)r, maybe you meant %(closest)r? (cho
- .__call__() not defined
- invalid option name {option_string!r} for BooleanOptionalAct
- nargs for store actions must be != 0; if you have nothing to
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/861f310b0daa2355.
Report an issue: GitHub.