【问题标题】:Is there a way to accept argument choices as a single string有没有办法接受参数选择作为单个字符串
【发布时间】:2019-08-13 00:27:11
【问题描述】:

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

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

最初我对每条元数据都有一个参数。 -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 选择以获得编程优势,包括错误选项的错误和格式良好的帮助,但我对其他方法持开放态度。非常感谢任何指导。

【问题讨论】:

  • 您是否考虑过为 type=... 而不是 Choices=... 编写自定义函数?
  • 您可能没有意识到您的 original 命令也可以调用为script.py -dtlsbrfcva?
  • 我在文档中读到 type 可以接受一个函数,但我不太了解如何使用它而不是选择。你能详细说明一下吗?
  • -m dtlsbrfcva 与简单的 -m 可选。之后您只需要解析字符串值。您不必在argparse 中做所有事情!
  • 是的,我确实参考了它,并且已经完成了工作,但是那时我不确定如果提供了错误的值和帮助消息是一半,如何获取命令行错误消息首先使用 argparse 的动机。

标签: python argparse


【解决方案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...')

【讨论】:

  • 好的。我明白了。我确实找到了一个类似的例子,但并没有完全与我自己的场景建立联系。谢谢。
  • 有两件事我无法从中弄清楚:我尝试设置 default='e' 如果 -m 没有提供任何选项并且它保持为空。另外,我将如何替换通常由选项设置填充的帮助输出中的字符串。目前它显示 [M],我想让它显示 dtlsbrfcvae 甚至 d、t、l、s、b、r、f。 c、v、a、e。再次感谢。
  • @mg 我已经用有关设置默认值的信息更新了我的 anwser。关于帮助,argparse 显然无法自动检测外部函数的作用。获得体面使用说明的唯一方法是编写有用的help=... 描述。如果它太长,您可能需要包含类似“见下文”的内容并将详细信息放入 epilog= 部分。
  • 一个字符串默认值在最后通过type 函数传递 - 如果它没有被用户输入替换。非字符串默认值保持原样。 const 参数也是如此。
  • @hpaulj 谢谢,我试图简明扼要,但最终得到了一个误导性的半真半假。关于默认值的部分已更新。
【解决方案2】:

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

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

已经可以等效地调用为:

script.py -dtlsbrfcva

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

【讨论】:

  • 感谢您的反馈。我一开始确实是这样用的,但后来我开始添加目录递归等功能,并想为此使用 -r,然后添加了一个函数来简单地计算每天添加的文件,并想窃取 -c for等等……我相信你看到了我发现自己的兔子洞,这就是导致我尝试将所有元数据选项推入单个主选项的子选项的原因。
【解决方案3】:

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

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

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

-m 假定 -me 变为 -mdtlsbrfcvae 并转换为列表。
不管给出什么,如果字符串中有'e',设置列表等于所有metadata_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

【讨论】:

    【解决方案4】:

    您可以循环调用每个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 并为每个字段设置长名称。您也可以组合短标志并保留顺序。

    【讨论】:

    • 这很漂亮。我仍然可以在这里使用它,但在这种情况下,我实际上希望保留 -m 来指定元数据并减少输入的数量(我个人不喜欢长名称),同时保留这些相同的字母来修改程序行为而不是使用它们仅用于元数据显示。
    猜你喜欢
    • 1970-01-01
    • 2022-06-10
    • 2019-10-21
    • 2013-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    相关资源
    最近更新 更多