This issue is discussed in depth in http://bugs.python.org/issue9334. Most of the activity was in 2011. I added a patch last year, but there's quite a backlog of argparse
patches.
At issue is the potential ambiguity in a string like '--env'
, or "-s WHATEVER -e COOL STUFF"
when it follows an option that takes an argument.
optparse
does a simple left to right parse. The first --env
is an option flag that takes one argument, so it consumes the next, regardless of what it looks like. argparse
, on the other hand, loops through the strings twice. First it categorizes them as 'O' or 'A' (option flag or argument). On the second loop it consumes them, using a re
like pattern matching to handle variable nargs
values. In this case it looks like we have OO
, two flags and no arguments.
The solution when using argparse
is to make sure an argument string will not be confused for an option flag. Possibilities that have been shown here (and in the bug issue) include:
--env="--env" # clearly defines the argument.
--env " --env" # other non - character
--env "--env " # space after
--env "--env one two" # but not '--env "-env one two"'
By itself '--env'
looks like a flag (even when quoted, see sys.argv
), but when followed by other strings it does not. But "-env one two"
has problems because it can be parsed as ['-e','nv one two']
, a `'-e' flag followed by a string (or even more options).
--
and nargs=argparse.PARSER
can also be used to force argparse
to view all following strings as arguments. But they only work at the end of argument lists.
There is a proposed patch in issue9334 to add a args_default_to_positional=True
mode. In this mode, the parser only classifies strings as option flags if it can clearly match them with defined arguments. Thus '--one' in '--env --one' would be classed as as an argument. But the second '--env' in '--env --env' would still be classed as an option flag.
Expanding on the related case in
Using argparse with argument values that begin with a dash ("-")
parser = argparse.ArgumentParser(prog="PROG")
parser.add_argument("-f", "--force", default=False, action="store_true")
parser.add_argument("-e", "--extra")
args = parser.parse_args()
print(args)
produces
1513:~/mypy$ python3 stack16174992.py --extra "--foo one"
Namespace(extra='--foo one', force=False)
1513:~/mypy$ python3 stack16174992.py --extra "-foo one"
usage: PROG [-h] [-f] [-e EXTRA]
PROG: error: argument -e/--extra: expected one argument
1513:~/mypy$ python3 stack16174992.py --extra "-bar one"
Namespace(extra='-bar one', force=False)
1514:~/mypy$ python3 stack16174992.py -fe one
Namespace(extra='one', force=True)
The "-foo one" case fails because the -foo
is interpreted as the -f
flag plus unspecified extras. This is the same action that allows -fe
to be interpreted as ['-f','-e']
.
If I change the nargs
to REMAINDER
(not PARSER
), everything after -e
is interpreted as arguments for that flag:
parser.add_argument("-e", "--extra", nargs=argparse.REMAINDER)
All cases work. Note the value is a list. And quotes are not needed:
1518:~/mypy$ python3 stack16174992.py --extra "--foo one"
Namespace(extra=['--foo one'], force=False)
1519:~/mypy$ python3 stack16174992.py --extra "-foo one"
Namespace(extra=['-foo one'], force=False)
1519:~/mypy$ python3 stack16174992.py --extra "-bar one"
Namespace(extra=['-bar one'], force=False)
1519:~/mypy$ python3 stack16174992.py -fe one
Namespace(extra=['one'], force=True)
1520:~/mypy$ python3 stack16174992.py --extra --foo one
Namespace(extra=['--foo', 'one'], force=False)
1521:~/mypy$ python3 stack16174992.py --extra -foo one
Namespace(extra=['-foo', 'one'], force=False)
argparse.REMAINDER
is like '*', except it takes everything that follows, whether it looks like a flag or not. argparse.PARSER
is more like '+', in that it expects a positional
like argument first. It's the nargs
that subparsers
uses.
This uses of REMAINDER
is documented, https://docs.python.org/3/library/argparse.html#nargs
argparse
(or the Python interpreter) has any chance to well whether they're there or not. – Drolet