When I use Python's argparse or optparse command line argument parser, any unique prefix of an argument is considered valid, e.g.
$ ./buildall.py --help
usage: buildall.py [-h] [-f]
Build all repositories
optional arguments:
-h, --help show this help message and exit
-f, --force Build dirty repositories
works with --help
, --hel
, --he
for the help option as well as --forc
and --fo
for the force option.
Can this behavior be turned off somehow? I want to get an error message for incomplete arguments.
The ability to disable abbreviated long options was only added in Python 3.5. From the argparse
documentation:
The parse_args()
method by default allows long options to be abbreviated to a prefix, if the abbreviation is unambiguous (the prefix matches a unique option) ... This feature can be disabled by setting allow_abbrev to False
.
So if you're on Python 3.5, you can create your parser with allow_abbrev=False
:
parser = argparse.ArgumentParser(..., allow_abbrev=False)
If you're on optparse or pre-3.5 argparse, you just have to live with abbreviated options.
For those of us still stuck on python2.7 for whatever reason, this is a minimal change to locally disable prefix matching:
class SaneArgumentParser(_argparse.ArgumentParser):
"""Disables prefix matching in ArgumentParser."""
def _get_option_tuples(self, option_string):
"""Prevent argument parsing from looking for prefix matches."""
return []
Now instead of using argparse.ArgumentParser, just use SaneArgumentParser. Unlike chepner's answer, this does not require any modification to the argparse module. It is also a much smaller change. Hopefully other people stuck in python's past will find this useful.
Prior to Python 3.5, you would have to monkeypatch an undocumented ArgumentParser
method. Don't actually use this; it is untested and may not work with all versions (or any version) of Python. For entertainment purposes only.
import argparse
# This is a copy from argparse.py, with a single change
def _get_option_tuples(self, option_string):
result = []
# option strings starting with two prefix characters are only
# split at the '='
chars = self.prefix_chars
if option_string[0] in chars and option_string[1] in chars:
if '=' in option_string:
option_prefix, explicit_arg = option_string.split('=', 1)
else:
option_prefix = option_string
explicit_arg = None
for option_string in self._option_string_actions:
# === This is the change ===
# if option_string.startswith(option_prefix):
if option_string == option_prefix:
action = self._option_string_actions[option_string]
tup = action, option_string, explicit_arg
result.append(tup)
# single character options can be concatenated with their arguments
# but multiple character options always have to have their argument
# separate
elif option_string[0] in chars and option_string[1] not in chars:
option_prefix = option_string
explicit_arg = None
short_option_prefix = option_string[:2]
short_explicit_arg = option_string[2:]
for option_string in self._option_string_actions:
if option_string == short_option_prefix:
action = self._option_string_actions[option_string]
tup = action, option_string, short_explicit_arg
result.append(tup)
elif option_string.startswith(option_prefix):
action = self._option_string_actions[option_string]
tup = action, option_string, explicit_arg
result.append(tup)
# shouldn't ever get here
else:
self.error(_('unexpected option string: %s') % option_string)
# return the collected option tuples
return result
argparse.ArgumentParser._get_option_tuples = _get_option_tuples
p = argparse.ArgumentParser()
p.add_argument("--foo")
print p.parse_args("--f 5".split())