argparse中nargs的范围

1 投票
3 回答
2104 浏览
提问于 2025-04-18 03:24

我有一个脚本,可以把多个视频和音频文件合并在一起。现在我有一个参数,可以接受四个不同的值:

# -A FILENAME LANGUAGE POSITION SPEED
$ script.py [... more parameters ...] -A audio.mp3 eng -1 1 [... more parameters ...]

现在我想让第三个和第四个值变成可选的。目前我有两个想法,但也许还有更好的解决办法:

  • nargs 设置为 +,如果提供的参数数量是1个或者超过4个,就报错。也许可以通过 type 参数来捕捉这个问题。不过这样的话,在帮助文档里就看不到需要2到4个值的说明了。
  • 为所有组合设置4个不同的参数。这样就可以让位置变成可选的。但问题是我需要四个参数名。

这个参数可能会出现多次(actionappend)。

3 个回答

0

根据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 是不工作的。

1

我建议让 -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='+',那么想要把帮助信息格式化成你想要的样子就会非常麻烦。

1

我觉得你想要实现的功能有:

  • 允许用户输入2、3或4个参数。使用'+'可以做到这一点。

  • 告诉用户他们可以输入多少个参数。如果代码没有按你想的那样工作,你可以自定义一个usagedescriptionhelp来说明。

  • 如果用户输入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所说的内容,可以看看那个问题。

撰写回答