argparse optional positional argument and subparse

2020-03-30 03:22发布

I have a python script that takes in an optional positional argument and has a few subcommands. Some of these subcommands require the positional argument, some don't. The problem I have appears when I try to use a subcommand that does not require the positional argument. Consider the following test file:

import argparse

argp = argparse.ArgumentParser()
argp.add_argument('inputfile', type=str, nargs='?',
                  help='input file to process')
argp.add_argument('--main_opt1', type=str,
                  help='global option')

subp = argp.add_subparsers(title='subcommands',
                           dest='parser_name',
                           help='additional help',
                           metavar="<command>")

tmpp = subp.add_parser('command1', help='command1 help')
tmpp.add_argument('pos_arg1', type=str,
                  help='positional argument')

print repr(argp.parse_args())

When I try to use the subcommand command1 with the first argument everything goes well.

macbook-pro:~ jmlopez$ python pytest.py filename command1 otherarg
Namespace(inputfile='filename', main_opt1=None, parser_name='command1', pos_arg1='otherarg')

But now let us assume that command1 doesn't need the first positional argument.

macbook-pro:~ jmlopez$ python pytest.py command1 otherarg
usage: pytest.py [-h] [--main_opt1 MAIN_OPT1] [inputfile] <command> ...
pytest.py: error: argument <command>: invalid choice: 'otherarg' (choose from 'command1')

I was somehow expecting inputfile to be set to None. Is there any way that argparse can predict that command1 is actually a subcommand and thus inputfile should be set to None?

2条回答
够拽才男人
2楼-- · 2020-03-30 03:52

you need to tell the parser that the first argument is different type. try adding flags option and default None value like this:

argp.add_argument('-i','--inputfile', type=str, nargs='?',
              help='input file to process',default=None)

now, you need to add -i before the inputfile argument, but it will work fine.

macbook-pro:~ jmlopez$ python pytest.py -i filename command1 otherarg
Namespace(inputfile='filename', main_opt1=None, parser_name='command1', pos_arg1='otherarg')

and

macbook-pro:~ jmlopez$ python pytest.py command1 otherarg
Namespace(inputfile=None, main_opt1=None, parser_name='command1', pos_arg1='otherarg')
查看更多
三岁会撩人
3楼-- · 2020-03-30 04:02

To argp the subparser argument looks just like another positional, one that takes choices (the names of the subparsers). Also argp knows nothing about pos_arg1. That's in tmpp's list of arguments.

When argp sees filename command1 otherarg, filename and command1 satisfy its 2 positionals. otherarg is then passed on the tmpp.

With command1 otherarg, again 2 strings, 2 argp positionals. command is assigned to inputfile. There's no logic to backtrack and say command1 fits subcommands better, or that `tmpp' needs one of those strings.

You could change the 1st positional to an optional, --inputfile.

Or you could inputfile another positional of tmpp. If a number of the subparsers need it it, consider using parents.

argparse isn't a smart as you, and can't 'think ahead' or 'backtrack'. If it appears to do something smart it's because it uses re pattern matching to handle nargs values (e.g. ?, *, +).

EDIT

One way to 'trick' argparse into recognizing the first positional as the subparser is to insert an optional after it. With command1 -b xxx otherarg, -b xxx breaks up the list of positional strings, so only command1 is matched against inputfile and subcommands.

p=argparse.ArgumentParser()
p.add_argument('file',nargs='?',default='foo')
sp = p.add_subparsers(dest='cmd')
spp = sp.add_parser('cmd1')
spp.add_argument('subfile')
spp.add_argument('-b')

p.parse_args('cmd1 -b x three'.split())
# Namespace(b='x', cmd='cmd1', file='foo', subfile='three')

The issue here is how argparse handles postionals with variable nargs. The fact that the 2nd positional is a subparser is not important. While argparse allows variable length positionals in any order, how it handles them can be confusing. It's easier to predict what argparse will do if there is only one such positional, and it occurs at the end.

查看更多
登录 后发表回答