Can't Get Argparse to Read Quoted String with Dashes in It

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



Leave a reply



Submit