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 问题,但似乎没有人回答这个问题:Can argparse be made to permute argument order like getopt?

用例是一个原型命令行签名,如 GNU 排序:

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 上面)。
  • p.add_argument('files',nargs=argparse.REMAINDER) -- 选项 -z 包含在文件中,除非在位置参数之前
  • p.add_argument('files',nargs='*',action='append');

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

4

3 回答 3

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因此它将为这些非标志参数存储错误的值。作为一种解决方法,您可以nargs='*'在使用interleaved_parse.

于 2012-08-16T11:24:22.613 回答
3

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

  1. 已经有一个明确命名为“ getopt ”的模块:

    注意 该getopt模块是一个命令行选项解析器,其 API 旨在让 Cgetopt()函数的用户熟悉。不熟悉 Cgetopt()函数或希望编写更少代码并获得更好帮助和错误消息的用户应该考虑改用该argparse模块。

  2. 即使 getopt 的默认值也不会置换,还有一个更明确定义的方法,名为gnu_getopt()

    此功能与 类似getopt(),但默认使用 GNU 样式的扫描模式。这意味着选项和非选项参数可以混合使用。

  3. 在 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']

编辑 1:使用 argparse 进行自我置换

受您链接到的“使用 Getopt”页面中“置换”定义的启发,

默认设置是在扫描时置换 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)
于 2012-04-09T07:41:36.043 回答
0

Python 3.7通过“parse_intermixed_args”添加了对此的支持 - 请参阅https://docs.python.org/3.7/library/argparse.html#argparse.ArgumentParser.parse_intermixed_args

于 2022-01-27T14:26:04.457 回答