Can't get argparse to read quoted string with dashes in it?
You can start the argument with a space python tst.py -e ' -e blah'
as a very simple workaround. Simply lstrip()
the option to put it back to normal, if you like.
Or, if the first "sub-argument" is not also a valid argument to the original function then you shouldn't need to do anything at all. That is, the only reason that python tst.py -e '-s hi -e blah'
doesn't work is because -s
is a valid option to tst.py
.
Also, the optparse module, now deprecated, works without any issue.
Make argparse treat dashes and underscore identically
parser.add_argument
accepts more than one flag for an argument (link to documentation). One easy way to make the parser accept both variants is to declare the argument as
parser.add_argument('--use-unicorns', '--use_unicorns', action='store_true')
However both options will show up in the help, and it is not very elegant as it forces one to write the variants manually.
An alternative is to subclass argparse.ArgumentParser
to make the matching invariant to replacing dashes by underscore. This requires a little bit of fiddling, as both argparse_ActionsContainer._parse_optional
and argparse_ActionsContainer._get_option_tuples
have to be modified to handle this matching and abbrevations, e.g. --use_unic
.
I ended up with the following subclassed method, where the matching to abbrevations is delegated from _parse_optional
to _get_option_tuples
:
from gettext import gettext as _
import argparse
class ArgumentParser(argparse.ArgumentParser):
def _parse_optional(self, arg_string):
# if it's an empty string, it was meant to be a positional
if not arg_string:
return None
# if it doesn't start with a prefix, it was meant to be positional
if not arg_string[0] in self.prefix_chars:
return None
# if it's just a single character, it was meant to be positional
if len(arg_string) == 1:
return None
option_tuples = self._get_option_tuples(arg_string)
# if multiple actions match, the option string was ambiguous
if len(option_tuples) > 1:
options = ', '.join([option_string
for action, option_string, explicit_arg in option_tuples])
args = {'option': arg_string, 'matches': options}
msg = _('ambiguous option: %(option)s could match %(matches)s')
self.error(msg % args)
# if exactly one action matched, this segmentation is good,
# so return the parsed action
elif len(option_tuples) == 1:
option_tuple, = option_tuples
return option_tuple
# if it was not found as an option, but it looks like a negative
# number, it was meant to be positional
# unless there are negative-number-like options
if self._negative_number_matcher.match(arg_string):
if not self._has_negative_number_optionals:
return None
# if it contains a space, it was meant to be a positional
if ' ' in arg_string:
return None
# it was meant to be an optional but there is no such option
# in this parser (though it might be a valid option in a subparser)
return None, arg_string, None
def _get_option_tuples(self, option_string):
result = []
if '=' in option_string:
option_prefix, explicit_arg = option_string.split('=', 1)
else:
option_prefix = option_string
explicit_arg = None
if option_prefix in self._option_string_actions:
action = self._option_string_actions[option_prefix]
tup = action, option_prefix, explicit_arg
result.append(tup)
else: # imperfect match
chars = self.prefix_chars
if option_string[0] in chars and option_string[1] not in chars:
# short option: if single character, can be concatenated with arguments
short_option_prefix = option_string[:2]
short_explicit_arg = option_string[2:]
if short_option_prefix in self._option_string_actions:
action = self._option_string_actions[short_option_prefix]
tup = action, short_option_prefix, short_explicit_arg
result.append(tup)
underscored = {k.replace('-', '_'): k for k in self._option_string_actions}
option_prefix = option_prefix.replace('-', '_')
if option_prefix in underscored:
action = self._option_string_actions[underscored[option_prefix]]
tup = action, underscored[option_prefix], explicit_arg
result.append(tup)
elif self.allow_abbrev:
for option_string in underscored:
if option_string.startswith(option_prefix):
action = self._option_string_actions[underscored[option_string]]
tup = action, underscored[option_string], explicit_arg
result.append(tup)
# return the collected option tuples
return result
A lot of this code is directly derived from the corresponding methods in argparse
(from the CPython implementation here). Using this subclass should make the matching of optional arguments invariant to using dashes -
or underscores _
.
argparse: parsing -h(some string here): changing default behavior
Actually this behavior has nothing to do with '-h' being help, but rather that is is a flag that takes 0 arguments:
In [1]: import argparse
In [2]: parser = argparse.ArgumentParser()
In [3]: parser.add_argument('-f', action='store_true')
In [4]: parser.parse_args(['-f'])
Out[4]: Namespace(f=True)
In [5]: parser.parse_args(['-fa'])
usage: ipython3 [-h] [-f]
ipython3: error: argument -f: ignored explicit argument 'a'
This error is raised by the core function that processes short optionals. '-f' is recognized as a short optional that takes 0 arguments. So it can't be parsed as '-f a'. And since '-a' has not been defined, it can't parse it as '-f -a'.
Here's an explicit way of define a help:
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-h', '--help', action='help')
It behaves the same as the default, but can be customized, either with different flag strings, or a different action
. Writing a custom action
class is more involved than I want get into now, but you can get ideas by looking at the _HelpAction
subclass in the argparse.py
file.
I was going to suggest making it store_true
and doing the help processing after, but I already showed that store_true
has this same problem with an extra string.
Your could try:
In [21]: parser = argparse.ArgumentParser(add_help=False)
In [22]: parser.add_argument('-h', '--help', nargs='?', default=False, const=Tru
...: e);
In [23]: parser.parse_args(['-h'])
Out[23]: Namespace(help=True)
In [24]: parser.parse_args([])
Out[24]: Namespace(help=False)
In [25]: parser.parse_args(['-ha'])
Out[25]: Namespace(help='a')
In [26]: parser.parse_args(['-h','a'])
Out[26]: Namespace(help='a')
Argparse `append` not working as expected
The problem isn't that -A
isn't allowed to be called more than once. It's that the -t
is seen as a separate option, not an argument to the -A
option.
As a crude workaround, you can prefix a space:
python my_program.py \
-A " -k filepath" \
-A " -t"
Given the following Minimal, Complete and Verifiable Example:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-A', '--module-args',
help="Arg to be passed through to the specified module",
action='append')
args = parser.parse_args()
print repr(args.module_args)
...that usage returns:
[' -k filepath', ' -t']
whereas leaving off the leading spaces reproduces your error.
Related Topics
Naturally Sorting Pandas Dataframe
How to Create a "View" on a Python List
How to Get Rid of Double Backslash in Python Windows File Path String
How to Resolve Typeerror: Can Only Concatenate Str (Not "Int") to Str
Matplotlib Fill Between Multiple Lines
Does a File Object Automatically Close When Its Reference Count Hits Zero
Pylab.Ion() in Python 2, Matplotlib 1.1.1 and Updating of the Plot While the Program Runs
Split a String with Unknown Number of Spaces as Separator in Python
"Pythonic" Method to Parse a String of Comma-Separated Integers into a List of Integers
How to Use 'Else' in a List Comprehension
How to Install Pycrypto on Windows
How to Do Multiple Arguments to Map Function Where One Remains the Same
Convert to Binary and Keep Leading Zeros
How to Sort Objects by Multiple Keys
How to Set Up a Virtual Environment for Python in Visual Studio Code