有没有办法将参数选择作为单个字符串接受

时间:2019-08-11 05:52:13

标签: python argparse

我想处理给定参数的字符串选择,而不是多次指定参数或使用空格。

我有一个显示视频文件元数据的脚本。大多数时候,我只需要文件列表,但偶尔我需要查看文件的持续时间,大小,比特率​​等。

最初,每个元数据都有一个参数。 -l表示长度或持续时间,-d表示创建日期,-m表示修改日期,-b表示比特率,-r表示分辨率,-c表示音频通道,-s大小,-e表示所有内容,等等...我可以指定一些或全部或不指定任何内容,然后完全获取我想要的信息,但是参数列表开始变得非常不规则,并且当我添加了要显示的元数据和其他功能时,我开始用尽逻辑字母分配,不得不开始交换参数名称我想要一个更好的方法。

我希望简化元数据的表达,并考虑将一些参数合并为一个参数,并减少最终命令和所涉及类型的长度。

具体来说,我正试图将其变为:

script.py -d -t -l -s -b -r -f -c -v -a

对此:

script.py -m dtlsbrfcva

这是我当前的功能:

def get_arguments():
    parser = argparse.ArgumentParser(description=DESCRIPTION)
    parser.add_argument('-m', action='append', nargs='+', choices=['d','t','l','s','b','r','f','c','v','a','e'],help='Display metadata for each file. Choices: (d)ate, (t)ime, (l)ength, (s)ize, (b)itrate, (r)esolution, (f)ramerate, (c)hannels, (v)ideo codec, (a)spect ratio, (e)verything')
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    if len(args.files) == 0:
        args.files="."

    return args

使用选择似乎是解决问题的方法,但是当我对动作使用“ append”而对nargs使用“ +”时,我要么需要重新指定参数

script.py -md -mt -ml -ms -mb -mr -mf -mc -mv -ma

更糟的是... 或使用空格

script.py -m d t l s b r f c v a

我猜哪个更好?

但是我确实得到了有益的帮助:

 -m {d,t,l,s,b,r,f,c,v,a,e} [{d,t,l,s,b,r,f,c,v,a,e} ...]
                        Display metadata for each file. Choices: 
                        (d)ate, (t)ime, (l)ength, (s)ize, (b)itrate, 
                        (r)esolution, (f)ramerate, (c)hannels, (v)ideo codec, 
                        (a)spect ratio, (e)verything

现在要弄清楚,如果我使用

add_argument('-m', action="store", help='Display metadata for each file. Choices: (d)ate, (t)ime, (l)ength, (s)ize, (b)itrate, (r)esolution, (f)ramerate, (c)hannels, (v)ideo codec, (a)spect ratio, (e)verything')

相反,我可以获得一个可以拆分和处理的字符串,但是这种方式的用处不大……

 -m M          Display metadata for each file. Choices: (d)ate, (t)ime,
                (l)ength, (s)ize, (b)itrate, (r)esolution, (f)ramerate,
                (c)hannels, (v)ideo codec, (a)spect ratio, (e)verything

我也研究了子解析器,但是从阅读的角度来看,我只会将问题扩展到不同的代码层,并使我的帮助输出的用处不大。我很高兴在这方面进行纠正。

理想情况下,我希望采用argparse的选择来获得编程优势,包括错误的选项错误和格式正确的帮助,但是我愿意接受其他方法。任何指导都将不胜感激。

4 个答案:

答案 0 :(得分:1)

这将接受-m,然后可能是由对应于选项的字母组成的单个字符串。

import argparse

MCHOICES = 'dtlsbrfcvae'
def msplit(marg):
    mlist = list(marg)
    for ch in mlist:
        if ch not in MCHOICES:
            raise argparse.ArgumentTypeError(f"{ch} is not a valid choice")
    return mlist


parser = argparse.ArgumentParser(description="<put description here>")
parser.add_argument('-m', type=msplit, nargs='?', const=[], default=[], help='Display metadata for each file...')

# some examples:
args = parser.parse_args("-m dtlsv".split())
print(args)
args = parser.parse_args("-m".split()) # const=... is used in this case (bare -m)
print(args)
args = parser.parse_args("".split()) # default=... is used in this case (no -m at all)
print(args)

更新:

设置默认值时,将字符串作为参数进行处理。非字符串直接分配,mlist不处理它们。例如。要使“ -m e”成为默认的元数据选择,请使用default='e'default=['e'](以及const=...)。 (感谢@hpaulj的评论)


此替代方法使带有选择项的字符串成为必需项:

parser.add_argument('-m', type=msplit, default=[], help='Display metadata for each file...')

答案 1 :(得分:0)

传递堆叠在一起的各个选项是完全可以接受的。因此:

script.py -d -t -l -s -b -r -f -c -v -a

可以等效地被调用为:

script.py -dtlsbrfcva

这可能无需任何更改即可解决您的问题。

答案 2 :(得分:0)

万一有人好奇,下面是我(可能)不必要的复杂实现。

就行为而言,我的选项列表位于字典中,因此我可以添加或删除项目,而无需修改任何argparse代码或帮助字符串。它为帮助信息构建必要的字符串,并生成一个列表以与字典进行比较。

然后将输入字符串与可用选项列表进行比较,并构建一个新列表以按照某些规则适当地迭代和执行功能。

-m假定-me成为-mdtlsbrfcvae并转换为列表。
不管给出什么其他内容,如果字符串中根本没有'e',则将list设置为等于所有meta_choices。

-m和其他任何东西都会被适当地解析,并在必要时给出错误。

metadata_options={'d':'date',
                  't':'time',
                  'l':'length',
                  's':'size',
                  'b':'bitrate',
                  'r':'resolution',
                  'f':'framerate',
                  'c':'channels',
                  'v':'video codec',
                  'a':'aspect ratio',
                  'e':'everything'}

def metadata_option_extractor():
    metadata_choices=[]
    metadata_options_string=''
    punct=''
    last=len(metadata_options)
    count=0
    for choice,description in metadata_options.items():
        metadata_choices.append(choice)
        word=list(description)
        word.insert(0, '(')
        word.insert(2, ')')
        description = "".join(word)
        count += 1
        if count < last:
            punct=','
        else:
            punct='.'
        metadata_options_string += description + punct + ' '
    return metadata_choices,metadata_options_string

metadata_choices,metadata_options_string=metadata_option_extractor()

def msplit(mlist):
    new_mlist=[]
    for ch in mlist:
        if ch not in metadata_choices:
            raise argparse.ArgumentTypeError(f"invalid choice: '{ch}' in '{mlist}' (choose from "+str(metadata_choices)+")")

        else:
            new_mlist.append(ch)

    if 'e' in new_mlist:
        new_mlist = metadata_choices

    return new_mlist

def get_arguments():
    parser = argparse.ArgumentParser(description=DESCRIPTION)
    parser.add_argument('-g', action='store', default='d', nargs=1, choices=['d', 'l', 't', 'b'], help='Group by various criteria. (d)ate, (t)ime, (l)ength, (b)itrate. Default: Date')
    parser.add_argument('-m', type=msplit, nargs='?', const='e', help='Display metadata for each file. When given without any options it is taken to mean all options. Choices: '+metadata_options_string)
    parser.add_argument('-n', nargs='*', action='store', help='Go back only n many groups (typically days). A second argument may be added to limit the number of grouped results.', type=int)
    parser.add_argument('-r', action='store_true', help='Recurse subdirectories.')
    parser.add_argument('-s', action='store', help='Search for "Word(s)", separated by commas, in filenames.', type=str)
    parser.add_argument('-a', action='store_true', help='Switch search method from OR to AND. Requires -s')
    parser.add_argument('-x', action='store_true', help='Export filenames in quotes as a single line for use with another program.')
    parser.add_argument('-c', action='store_true', help='Count number of videos by group rather than list the videos.')

    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Default behaviour
    if len(args.files) == 0:
        args.files="."

    return args

答案 3 :(得分:0)

您可以循环调用每个parser.add_argument,它将正常工作。

类似的事情会起作用:

METADATA_FIELDS = {
    'b': 'bitrate',
    # ... put the rest here
}

# Some other code, probably

parser = argparse.ArgumentParser(
    # ... blah
)

for flag, name in METADATA_FIELDS.items():
    parser.add_argument(
        '-' + flag, '--' + name,
        dest='fields',
        action='append_const',
        const=name,
        help="Show {}".format(name)
    )

parser.add_argument(
    '-e', '--everything',
    dest='fields',
    action='store_const',
    const=list(METADATA_FIELDS.values())
)

args = parser.parse_args()

这样做可以切出-m并为每个字段使用长名称。您还可以组合短标记,并且订单将被保留。