Python argparse 中的 REMAINDER 不明确

9 投票
4 回答
14257 浏览
提问于 2025-04-17 19:31

根据文档的说明:

argparse.REMAINDER。所有剩下的命令行参数会被收集到一个列表中。这在一些命令行工具中很有用,因为它们需要把参数传递给其他命令行工具:

>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('--foo')
>>> parser.add_argument('command')
>>> parser.add_argument('args', nargs=argparse.REMAINDER)
>>> print parser.parse_args('--foo B cmd --arg1 XX ZZ'.split())
Namespace(args=['--arg1', 'XX', 'ZZ'], command='cmd', foo='B')

我试着用这个来达到同样的目的,但在某些情况下,它对我来说似乎有点问题 (或者我可能理解错了这个概念)

import argparse

a = argparse.ArgumentParser()

a.add_argument('-qa', nargs='?')
a.add_argument('-qb', nargs='?')
a.add_argument('rest', nargs=argparse.REMAINDER)

a.parse_args('-qa test ./otherutil bar -q atr'.split())

结果:

test.py: error: ambiguous option: -q could match -qa, -qb

所以显然,如果 otherutil 有一些参数和传给 argparse 的参数“冲突”,它似乎就不能正常工作了。

我本来期待当 argparse 遇到 REMAINDER 这种参数时,它会直接把列表末尾的所有字符串都用上,而不再进行进一步的解析。我能以某种方式实现这个效果吗?

4 个回答

4

你需要使用两个 --

a.add_argument('--qa', nargs='?')
a.add_argument('--qb', nargs='?')

这样你定义的选项就不会和一个叫 -q 的选项冲突,后者需要至少一个参数,这个参数是在别的地方定义的。

来自 argparse 文档

ArgumentParser.add_argument(name or flags...)

name or flags - Either a name or a list of option strings, e.g. foo or -f, --foo.

编辑:回复 @PDani 的第一个评论:

这篇帖子很有意思。

根据我的理解,argparse 遵循 POSIX 和 GNU 的风格。

一个重要的点是,短选项(一个字母的选项)可以组合在一起,如果某个选项需要一个参数,这个参数可以直接跟在选项字母后面。例如,如果你有这样的情况:

a.add_argument('-a', action='store_true')
a.add_argument('-b', action='store_true')
a.add_argument('-c', action='store_true')
a.add_argument('-d', nargs=1)
a.add_argument('-e', nargs=1)

你可以这样调用它们 -abcd3 -e5 或者 -a -b -c -d3 -e5 或者 -cba -e5 -d3,等等。
现在,如果你有

a.add_argument('-abc',  action='store_true')

那么对于 argparse 来说,很难判断 -abc 是三个短选项组合在一起,还是一个长选项。所以你必须把参数定义为 --abc

所以我想你不能用一个 - 来表示长选项名。

我知道还有一种替代的命令行解析方式,叫做 docopt:你可以看看,但我怀疑它能解决你的问题。

7

这段内容主要讲的是如何处理缩写,而不是关于 REMAINDER nargs 的问题。

In [111]: import argparse                                                                 
In [112]: a = argparse.ArgumentParser() 
     ...:  
     ...: a.add_argument('-qa', nargs='?') 
     ...: a.add_argument('-qb', nargs='?')                                                

In [113]: a.parse_args('-qa test ./otherutil bar -q atr'.split())                         
usage: ipython3 [-h] [-qa [QA]] [-qb [QB]]
ipython3: error: ambiguous option: -q could match -qa, -qb

argparse 这个工具会进行两次解析。第一次,它会尝试把输入的字符串分成选项(标志)和参数。第二次,它会交替解析位置参数和可选参数,并根据 nargs 来分配参数。

这里的模糊性发生在第一次解析时。它正在尝试将 '-q' 匹配到两个可用的选项中。而 REMAINDER 的特殊处理(把 '-q' 当作普通字符串来处理)是在第二次解析时才会发生。

更新版的 argparse 允许我们关闭缩写处理:

In [114]: a.allow_abbrev                                                                  
Out[114]: True
In [115]: a.allow_abbrev=False                                                            
In [116]: a.parse_args('-qa test ./otherutil bar -q atr'.split())                         
usage: ipython3 [-h] [-qa [QA]] [-qb [QB]]
ipython3: error: unrecognized arguments: ./otherutil bar -q atr

如果我添加 REMAINDER 动作:

In [117]: a.add_argument('rest', nargs=argparse.REMAINDER) 

In [118]: a.parse_args('-qa test ./otherutil bar -q atr'.split())                         
Out[118]: Namespace(qa='test', qb=None, rest=['./otherutil', 'bar', '-q', 'atr'])

使用 '--' 就像 @Colin 建议的那样是有效的,因为这个字符串在第一次解析时就被识别了:

In [119]: a.allow_abbrev=True                                                             
In [120]: Out[117].nargs='*'                                                              
In [121]: a.parse_args('-qa test -- ./otherutil bar -q atr'.split())                      
Out[121]: Namespace(qa='test', qb=None, rest=['./otherutil', 'bar', '-q', 'atr'])
9

我在尝试把选项传递给一个底层工具时遇到了这个问题。最后我使用的解决方案是 nargs='*',而不是 nargs=argparse.REMAINDER。然后我用一个“伪参数” -- 来分隔我命令的选项和底层工具的选项:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--myflag', action='store_true')
>>> parser.add_argument('toolopts', nargs='*')
>>> parser.parse_args('--myflag -- -a --help'.split())
Namespace(myflag=True, toolopts=['-a', '--help'])

这样在帮助输出中记录起来也比较简单。

撰写回答