首页 文章

Python的argparse permute参数顺序可以像gnu getopt一样吗?

提问于
浏览
9

GNU getopt和使用它的命令行工具允许交叉选项和参数,称为置换选项(参见http://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html#Using-Getopt) . Perl的Getopt :: Long模块也支持这个(使用qw(:config gnu_getopt)) . argparse似乎不支持(甚至提及)置换选项 .

有许多与arg / opt命令相关的SO问题,但似乎没有人回答这个问题:argparse可以像getopt那样置换参数顺序吗?

用例是一个典型的命令行签名,如GNU sort:

sort [opts] [files]

其中1)选项和文件被置换,以及2)文件列表可以包含零个或多个参数 .

例如:

import argparse
p = argparse.ArgumentParser();
p.add_argument('files',nargs='*',default=['-']);
p.add_argument('-z',action='store_true')

p.parse_args(['-z','bar','foo']) # ok
p.parse_args(['bar','foo','-z']) # ok
p.parse_args(['bar','-z','foo']) # not okay
usage: ipython [-h] [-z] [files [files ...]]

我试过了:

  • p.parse_known_args - 实际上也不会对其进行置换,并且它不会忽略看起来像无效选项的参数(例如,--bogus或-b above) .

  • p.add_argument('files',nargs = argparse.REMAINDER) - 选项-z包含在文件中,除非在位置参数之前

  • p.add_argument('files',nargs = '*',action = 'append');

我想实现一些接近上面GNU排序原型的东西 . 我对可以为每个文件指定的标志不感兴趣(例如,-f file1 -f file2) .

2 回答

  • 4

    这是一个快速的解决方案,它一次解码参数列表一(选项,位置参数)对 .

    import argparse
    
    class ExtendAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            items = getattr(namespace, self.dest, None)
            if items is None:
                items = []
            items.extend(values)
            setattr(namespace, self.dest, items)
    
    parser = argparse.ArgumentParser()
    parser.add_argument('files', nargs='*', action=ExtendAction)
    parser.add_argument('-z', action='store_true')
    parser.add_argument('-v', action='count')
    parser.add_argument('args_tail', nargs=argparse.REMAINDER)
    
    def interleaved_parse(argv=None):
        opts = parser.parse_args(argv)
        optargs = opts.args_tail
        while optargs:
            opts = parser.parse_args(optargs, opts)
            optargs = opts.args_tail
        return opts
    
    print(interleaved_parse('-z bar foo'.split()))
    print(interleaved_parse('bar foo -z'.split()))
    print(interleaved_parse('bar -z foo'.split()))
    print(interleaved_parse('-v a -zv b -z c -vz d -v'.split()))
    

    输出:

    Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
    Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
    Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
    Namespace(args_tail=[], files=['a', 'b', 'c', 'd'], v=4, z=True)
    

    注意:不要尝试将此与其他非标志参数一起使用(除了单个 nargs='*' 参数和 args_tail 参数) . 解析器将不知道 parse_args 的先前调用,因此它将为这些非标志参数存储错误的值 . 作为解决方法,您可以在使用 interleaved_parse 后手动解析 nargs='*' 参数 .

  • 3

    我在argparse文档中没有看到任何明确的说明它可以或不可以置换 . 根据您自己的观察结果,排列失败,以及以下文档引用,我将得出结论,它无法完成 .

    • 那里's already a module explicitly named ' getopt':

    注意getopt模块是命令行选项的解析器,其API设计为C getopt()函数的用户熟悉 . 不熟悉C getopt()函数或想要编写更少代码并获得更好帮助和错误消息的用户应该考虑使用argparse模块 .

    • 即使getopt的默认值没有置换,也有一个名为gnu_getopt()的更明确定义的方法:

    除了默认使用GNU样式扫描模式外,此函数的工作方式与getopt()类似 . 这意味着可以混合选项和非选项参数 .

    • 在getopt文档中,通过包含以下内容进一步夸大了对argparse的上述引用:

    请注意,使用argparse模块可以生成具有更少代码和更多信息帮助和错误消息的等效命令行界面:

    再说一遍,没有确切的,但是,对我来说,getopt和argparse之间正在形成一个非常尖锐的鸿沟,文档偏向/倡导argparse .

    这是一个使用 gnu_getop() 的例子,它满足了你的 -z [file [file]] 测试:

    >>> args = 'file1 -z file2'.split()
    >>> args
    ['file1', '-z', 'file2']
    >>> opts, args = getopt.gnu_getopt(args, 'z')
    >>> opts
    [('-z', '')]
    >>> args
    ['file1', 'file2']
    

    Edit 1: Go Permute Yourself, with argparse

    灵感来自您链接到的“使用Getopt”页面中“permute”的定义,

    默认情况下,在扫描时会置换argv的内容,以便最终所有非选项都在最后 .

    如何在将arg字符串传递给 parse_args() 之前置换它?

    import argparse
    
    p = argparse.ArgumentParser();
    p.add_argument('files',nargs='*',default=['-']);
    p.add_argument('-z',action='store_true')
    

    滚动你自己:

    import re
    
    def permute(s, opts_ptn='-[abc]'):
        """Returns a permuted form of arg string s using a regular expression."""
        opts = re.findall(opts_ptn, s)
        args = re.sub(opts_ptn, '', s)
        return '{} {}'.format(' '.join(opts), args).strip()
    
    >>> p.parse_args(permute('bar -z foo', '-[z]').split())
    Namespace(files=['bar', 'foo'], z=True)
    

    利用getopt:

    import getopt
    
    def permute(s, opts_ptn='abc'):
        """Returns a permuted form of arg string s using `gnu_getop()'."""
        opts, args = getopt.gnu_getopt(s.split(), opts_ptn)
        opts = ' '.join([''.join(x) for x in opts])
        args = ' '.join(args)
        return '{} {}'.format(opts, args).strip()
    
    >>> p.parse_args(permute('bar -z foo', 'z').split())
    Namespace(files=['bar', 'foo'], z=True)
    

相关问题