Python argparse positional arguments and sub-commands
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional', nargs='?')
subparsers = parser.add_subparsers()
subparsers.add_parser('subpositional')
print(parser.parse_args(['positional', 'subpositional']))
# -> Namespace(positional='positional')
print(parser.parse_args(['subpositional']))
# -> Namespace(positional=None)
parser.print_usage()
# -> usage: bpython [-h] [positional] {subpositional} ...
The common practice is that arguments before the command (on the left side) belong to the main program, after (on the right) -- to the command. Therefore positional
should go before the command subpositional
. Example programs: git
, twistd
.
Additionally an argument with narg=?
should probably be an option (--opt=value
), and not a positional argument.
At first I thought the same as jcollado, but then there's the fact that, if the subsequent (top level) positional arguments have a specific nargs
(nargs
= None
, nargs
= integer), then it works as you expect. It fails when nargs
is '?'
or '*'
, and sometimes when it is '+'
. So, I went down to the code, to figure out what is going on.
It boils down to the way the arguments are split to be consumed. To figure out who gets what, the call to parse_args
summarizes the arguments in a string like 'AA'
, in your case ('A'
for positional arguments, 'O'
for optional), and ends up producing a regex pattern to be matched with that summary string, depending on the actions you've added to the parser through the .add_argument
and .add_subparsers
methods.
In every case, for you example, the argument string ends up being 'AA'
. What changes is the pattern to be matched (you can see the possible patterns under _get_nargs_pattern
in argparse.py
. For subpositional
it ends up being '(-*A[-AO]*)'
, which means allow one argument followed by any number of options or arguments. For positional
, it depends on the value passed to nargs
:
None
=>'(-*A-*)'
- 3 =>
'(-*A-*A-*A-*)'
(one'-*A'
per expected argument) '?'
=>'(-*A?-*)'
'*'
=>'(-*[A-]*)'
'+'
=>'(-*A[A-]*)'
Those patterns are appended and, for nargs=None
(your working example), you end up with '(-*A[-AO]*)(-*A-*)'
, which matches two groups ['A', 'A']
. This way, subpositional
will parse only subpositional
(what you wanted), while positional
will match its action.
For nargs='?'
, though, you end up with '(-*A[-AO]*)(-*A?-*)'
. The second group is comprised entirely of optional patterns, and *
being greedy, that means the first group globs everything in the string, ending up recognizing the two groups ['AA', '']
. This means subpositional
gets two arguments, and ends up choking, of course.
Funny enough, the pattern for nargs='+'
is '(-*A[-AO]*)(-*A[A-]*)'
, which works as long as you only pass one argument. Say subpositional a
, as you require at least one positional argument in the second group. Again, as the first group is greedy, passing subpositional a b c d
gets you ['AAAA', 'A']
, which is not what you wanted.
In brief: a mess. I guess this should be considered a bug, but not sure what the impact would be if the patterns are turned into non-greedy ones...