argparse可选位置参数和子解析器参数

4

我有一个Python脚本,它接受一个可选的位置参数并有一些子命令。其中一些子命令需要位置参数,而其他一些则不需要。当我尝试使用不需要位置参数的子命令时,出现了问题。请考虑以下测试文件:

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())

当我尝试使用子命令command1并带上第一个参数时,一切都很顺利。

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

现在我们假设command1不需要第一个位置参数。

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')

我有点预期inputfile应该设为None。是否有办法让argparse预测command1实际上是一个子命令,因此应将inputfile设置为None?

2个回答

5
argp将子解析器参数视为另一个位置参数,取值为选项(即子解析器的名称)。此外,argp对于pos_arg1一无所知。这在tmpp的参数列表中。

argp看到filename command1 otherarg时,filenamecommand1满足其2个位置参数。然后将otherarg传递给tmpp

对于command1 otherarg,同样是2个字符串,也是argp的2个位置参数。将command赋值给inputfile。没有回溯逻辑来判断command1更好地匹配subcommands,或者`tmpp'需要其中一个字符串。

您可以将第一个位置参数更改为可选项,--inputfile

或者,您可以将inputfile作为tmpp的另一个位置参数。如果多个子解析器需要它,请考虑使用parents

argparse不如您聪明,并且无法“向前”或“回溯”。如果它似乎做了一些聪明的事情,那是因为它使用re模式匹配来处理nargs值(例如?、*、+)。

编辑

欺骗argparse识别第一个位置参数作为子解析器的一种方法是在其之后插入一个可选项。对于command1 -b xxx otherarg-b xxx会将位置字符串列表分开,因此只有command1inputfilesubcommands匹配。

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')

这里的问题在于 argparse 如何处理带有可变 nargs 的位置参数。第二个位置参数是子解析器并不重要。虽然 argparse 允许任意顺序的可变长度位置参数,但它们的处理方式可能令人困惑。如果只有一个这样的位置参数,并且它出现在最后,那么预测 argparse 的行为更容易。


1
似乎是这样。我猜想实现我的需求的唯一方法就是预处理sys.argv,并在需要时插入默认输入文件或默认命令,然后让argparse完成其工作。 - jmlopez
subcommands之前出现inputfile很重要吗?通常(例如像git这样的程序),子解析器是第一个位置参数。如果inputfilecommand1以特殊方式处理,我希望将其命名为命令行的末尾附近。 - hpaulj
我已经添加了一个部分解决方案 - 向子解析器添加一个optional(标记)参数,以拆分positional字符串列表。 - hpaulj

0

你需要告诉解析器第一个参数的类型不同。 尝试添加标志选项和默认值None,像这样:

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

现在,您需要在输入文件参数之前添加-i,但它会正常工作。
macbook-pro:~ jmlopez$ python pytest.py -i filename command1 otherarg
Namespace(inputfile='filename', main_opt1=None, parser_name='command1', pos_arg1='otherarg')

并且

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

我之前可以这样做:macbook-pro:~ jmlopez$ python pytest.py _ command1 otherarg,以此告诉Python输入参数为空。但我更希望有一个选项,比如--cmd,来指定第一个关键字是命令而不是输入。 - jmlopez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接