argparse中nargs的范围
我有一个脚本,可以把多个视频和音频文件合并在一起。现在我有一个参数,可以接受四个不同的值:
# -A FILENAME LANGUAGE POSITION SPEED
$ script.py [... more parameters ...] -A audio.mp3 eng -1 1 [... more parameters ...]
现在我想让第三个和第四个值变成可选的。目前我有两个想法,但也许还有更好的解决办法:
- 把
nargs
设置为+
,如果提供的参数数量是1个或者超过4个,就报错。也许可以通过type
参数来捕捉这个问题。不过这样的话,在帮助文档里就看不到需要2到4个值的说明了。 - 为所有组合设置4个不同的参数。这样就可以让位置变成可选的。但问题是我需要四个参数名。
这个参数可能会出现多次(action
是 append
)。
3 个回答
根据chepner的回答,我开发了一个更高级的“子解析器”:
audio_parameters = [ "f", "l", "p", "s", "b", "o" ]
def audio_parser(value):
data = {
"l": None,
"p": -1,
"s": 1,
"b": None,
"o": 0,
}
found = set()
if value[0] in audio_parameters and value[1] == "=":
start = 0
while start >= 0:
end = start
parameter = value[start]
found.add(parameter)
#search for the next ',x=' block, where x is an audio_parameter
while end >= 0:
# try next ',' after the last found
end = value.find(",", end + 2)
# exit loop, when find, (or after non found)
if end >= 0 and value[end + 1] in audio_parameters and value[end + 1] not in found and value[end + 2] == "=":
end += 1
break
if parameter in audio_parameters:
parameter_value = value[start + 2:end - 1 if end > 0 else len(value)]
if parameter_value != "":
data[parameter] = parameter_value
start = end
else:
i = 0
for splitted in value.split(","):
if i >= len(audio_parameters):
return ArgumentTypeError("Too many arguments")
if len(splitted) > 0:
data[audio_parameters[i]] = splitted
i += 1
if "f" in data:
return data
else:
raise argparse.ArgumentTypeError("Too few arguments")
这个解析器支持你提到的 file[,lang[,pos[,speed]]]
,而且还可以更灵活地选择特定的值。比如说,如果你只想设置文件、语言和速度,可以用 f=file,s=speed,l=lang
,而且顺序可以随意。这也允许一些看起来像参数名的东西,即使它并不存在或者已经被使用过。简单版本可能会解析成 f=file,x=stillname,s=speed,l=lang
,在这里 f
参数就是 file,x=stillname
。它还允许像 f=file,f=overwrites
这样的写法,因为它只接受第一次出现的值。所以如果文件名里包含 ,b=
,你可以简单地写成 b=,f=file,b=haha
。
不过,像 file,l=lang
这样的混合模式是不可行的。正如你可能看到的,这个参数变得非常复杂,现在有6个子参数,这让每种组合都很难用一个参数名来表示。而像 '{n,m}' 这样的结构也不够灵活,因为你不能轻易省略某些值。
不过我注意到,带有 [] 的 metavar 是不工作的。
我建议让 -A
接受一个用逗号分隔的字符串(或者你可以选择其他分隔符),并为帮助信息提供一个自定义的变量名。
def av_file_type(str):
data = tuple(str.split(","))
n = len(data)
if n < 2:
raise ArgumentError("Too few arguments")
elif n == 2:
return data + (default_position, default_speed)
elif n == 3:
return data + (default_speed,)
elif n == 4:
return data
else:
return ArgumentError("Too many arguments")
p.add_argument("-A", action='append', type=av_file_type,
metavar='filename,language[,position[,speed]]')
如果使用 nargs='+'
,那么想要把帮助信息格式化成你想要的样子就会非常麻烦。
我觉得你想要实现的功能有:
允许用户输入2、3或4个参数。使用'+'可以做到这一点。
告诉用户他们可以输入多少个参数。如果代码没有按你想的那样工作,你可以自定义一个
usage
、description
或help
来说明。如果用户输入1个或超过4个参数,就要给出错误提示。你可以在三个地方检查输入:使用自定义的
type
、自定义的action
,或者在parse_args
之后。
type
在这里帮不了你,因为它是单独处理每个参数的。如果我输入p.parse_args('-A one two three'.split())
,type
函数会被调用3次,每次处理一个参数字符串。它不会把所有字符串放在一起看。
action
可能会有效,因为它能看到parse_args
认为-A
需要的所有参数值。这会把一个-A
和下一个-A
(或其他标志)之间的所有字符串都考虑进去。但因为你想要追加参数,你需要根据argparse._AppendAction
类来设计你的自定义动作。
在处理完后检查namespace
可能是你最好的选择。你会得到一个列表的列表,你可以检查每个子列表中的元素数量。你可以使用parse.error(your_message)
来生成一个符合argparse风格的错误信息。
关于启用nargs范围值的Python bug问题可以在这里找到:http://bugs.python.org/issue11354。我提出了一个补丁,允许使用nargs='{m,n}'
,这个设计是基于re
功能的。实际上,它最终使用re
匹配来将字符串分配给不同的动作。如果你想了解更多关于SethMMorton所说的内容,可以看看那个问题。