0

I'd like to pass an "argument" to argument.

I.e., in the following code:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-a")

print parser.parse_args(['-a', 'hi'])
print parser.parse_args(['-a', '-hi'])

The output is:

Namespace(a='hi')
usage: problem.py [-h] [-a A]
problem.py: error: argument -a: expected one argument

While I'd like it to be:

Namespace(a='hi')
Namespace(a='-hi')

How can I achieve that?

I've seen in the help the section 15.4.4.3. Arguments containing -, but it seems to support only negative numbers. Also, they suggest passing "--", but it's not good in my use case, but everything after the "--" isn't treated as argument (if I understand correctly).

But I want "-a" to consume only 1 argument, and then continue parsing the other arguments as real arguments.

How can it be done?

EDIT

Adding a space before the argument works:

print parser.parse_args(['-a', ' -hi'])

But is there a way to achieve that, without requiring the user to add spaces?

Zvika
  • 1,542
  • 2
  • 17
  • 21
  • I can't think of any usecase, that needs an argument value to be prefixed with -. Why don't you just pass in `hi` and prepend the dash when using the argument? – Igl3 Jan 18 '18 at 14:36
  • This script is a compilation wrapper, that calls another compilation script. It needs to allow the user to pass arguments to the 'lower-level' compilation script as-is. The wrapper isn't familiar with all the arguments of the lower level script, and can't know whether and when it needs a dash or not – Zvika Jan 18 '18 at 15:08
  • 1
    After some research I did not find a way to do what you say, seems like an inherent restriction of argparse. There were discussions about it like 7 years ago but looks like no progress is made, some of the standard modules are just abandoned. The best workaround seems to be using `=`, like `parse_args(['-a=-hi'])` – Tamas Hegedus Jan 18 '18 at 15:27
  • `optparse` is (and will remain) available as an alternative parser. As is the older `getopt`. – hpaulj Jan 18 '18 at 17:32

1 Answers1

1

Here's a parser subclass that implements the latest suggestion on the https://bugs.python.org/issue9334. Feel free to test it.

class ArgumentParserOpt(ArgumentParser):
    def _match_argument(self, action, arg_strings_pattern):
        # match the pattern for this action to the arg strings
        nargs_pattern = self._get_nargs_pattern(action)
        match = _re.match(nargs_pattern, arg_strings_pattern)

        # if no match, see if we can emulate optparse and return the
        # required number of arguments regardless of their values
        #
        if match is None:
            import numbers
            nargs = action.nargs if action.nargs is not None else 1
            if isinstance(nargs, numbers.Number) and len(arg_strings_pattern) >= nargs:
                return nargs

        # raise an exception if we weren't able to find a match
        if match is None:
            nargs_errors = {
                None: _('expected one argument'),
                OPTIONAL: _('expected at most one argument'),
                ONE_OR_MORE: _('expected at least one argument'),
            }
            default = ngettext('expected %s argument',
                               'expected %s arguments',
                               action.nargs) % action.nargs
            msg = nargs_errors.get(action.nargs, default)
            raise ArgumentError(action, msg)

        # return the number of arguments matched
        return len(match.group(1))

It replaces one method providing a fall back option when the regular argument matching fails.

If you and your users can live with it, the long flag fix is best --arg=-a is simplest. This unambiguously specifies -a as an argument to the --arg Action.

hpaulj
  • 221,503
  • 14
  • 230
  • 353