关于命令行:Python的argparse permute参数顺序可以像gnu getopt一样吗?

Can Python's argparse permute argument order like gnu getopt?

gnu getopt和使用它的命令行工具允许选项和参数交错,称为Permuting选项(请参见http://www.gnu.org/software/libc/manual/html_node/using getopt.html_using getopt)。Perl的getopt::long模块也支持这一点(使用qw(:config gnu_getopt))。argparse似乎不支持(甚至不提)排列选项。

有很多与arg/opt顺序相关的问题,但似乎没有一个能回答这个问题:argparse是否可以像getopt一样按排列参数顺序?

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

1
sort [opts] [files]

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

例如:

1
2
3
4
5
6
7
8
9
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 ...]]

我试过了:

  • parse-known-args——不会抱怨,但实际上也不会改变,也不会因为看起来像无效选项的参数而犹豫(例如,上面的--bogus或-b)。
  • p.add_参数('files',nargs=argparse.remals)--选项-z包含在文件中,除非位置参数之前
  • p.add_参数('files',nargs='*',action='append');

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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()))

输出:

1
2
3
4
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='*'参数。


我在argparse文档中没有看到任何明确的说明,说明它可以或不能排列。根据你自己的观察,如果排列失败,以及下面的文档引用,我将得出结论,这是不可能的。

  • 已经有一个显式命名为"getopt"的模块:


    Note The getopt module is a parser for command line options whose API
    is designed to be familiar to users of the C getopt() function. Users
    who are unfamiliar with the C getopt() function or who would like to
    write less code and get better help and error messages should consider
    using the argparse module instead.

  • 即使getopt的默认值不是permute,也有一个更明确定义的方法,名为gnu_getopt()


    This function works like getopt(), except that GNU style scanning mode
    is used by default. This means that option and non-option arguments
    may be intermixed.

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


    Note that an equivalent command line interface could be produced with
    less code and more informative help and error messages by using the
    argparse module:

  • 再一次,没有明确的内容,但是在我看来,getopt和argparse之间有一个非常明显的区别,文档支持/提倡argparse。

    下面是一个使用gnu_getop()的例子,它满足了您的-z

    ]测试:

    1
    2
    3
    4
    5
    6
    7
    8
    >>> args = 'file1 -z file2'.split()
    >>> args
    ['file1', '-z', 'file2']
    >>> opts, args = getopt.gnu_getopt(args, 'z')
    >>> opts
    [('-z', '')]
    >>> args
    ['file1', 'file2']

    编辑1:使用argparse进行自我排序

    灵感来源于你链接到的"使用getopt"页面中的"permute"定义,

    The default is to permute the contents of argv while scanning it so
    that eventually all the non-options are at the end.

    在将arg字符串传递给parse_args()之前,对它进行排列怎么样?

    1
    2
    3
    4
    5
    import argparse

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

    滚动您自己的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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)