在子解析器之后停止解析

Posted

技术标签:

【中文标题】在子解析器之后停止解析【英文标题】:Stop parsing after subparser 【发布时间】:2015-08-09 01:04:17 【问题描述】:

在我的 CLI 脚本中,我使用 argparse 来接收一些可选参数,然后是一个位置参数。位置参数用于确定要使用的子解析器,该子解析器又运行一个函数,该函数调用一个外部程序,该程序采用自己的参数。因此,命令行用法如下所示:

myscript [OPTIONS] subcommand [SUBCOMMAND_OPTIONS]

现在我的问题是我声明的 OPTIONS 与外部程序中声明的 SUBCOMMAND_OPTIONS 之间存在冲突。简单的解决方法是确保我重命名 myscript 中的所有冲突,但我不能对所有选项都这样做 - 最值得注意的是“-h”选项以获得帮助。理想情况下,我希望 argparse 在遇到子命令后立即停止解析,并将其余的 args 简单地传递给外部程序。

因此,以下调用应该显示 myscript 的帮助文本:

myscript -h

而相比之下,下面应该显示“bar”子解析器调用的外部程序的帮助文本:

myscript --foo bar -h

更多代码让上面的内容更清楚:

>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', action='store_true')
>>> subparsers = parser.add_subparsers()
>>> subparsers.add_parser("bar")

>>> parser.parse_known_args("--foo bar --test".split())
(Namespace(foo=True), ['--test'])
# cool - this is what I want, I'll just pass --test on to the external program

>>> parser.parse_known_args("--foo bar -h".split())
usage:  bar [-h]

optional arguments:
  -h, --help  show this help message and exit

# unfortunately the above argparse help message is NOT what I wanted, instead I was looking for the result below:
(Namespace(foo=True), ['-h'])

>>> parser.parse_known_args("bar --test -- -h".split())
# this works, sort of, it requires educating the end-user to use the '--' parameter and I'd like to avoid that if possible.
(Namespace(foo=False), ['--test', '--', '-h'])

【问题讨论】:

你试过argparse中的subparser机制吗?合身可能并不完美,但如果您阅读文档、尝试一下,然后再提出更多问题,这对我们所有人来说可能是最有成效的。 实际上我在上面的问题中明确提到了子解析器的使用,所以很明显我已经知道了。问题不是子解析命令的能力,而是遇到子命令后停止解析选项的能力。在你问我之前 - 是的,我已经在使用 argparse.parser_known_args() 来阻止 argparse 在它一无所知的外部命令参数上失败,问题在于 args 冲突 - 特别是。 -- 告诉它将后面的所有内容都视为位置参数,无论它们看起来是否像标志。 nargs=argparse.PARSERREMAINDEr 也可用于获取位置的“其他所有内容”。子解析器机制使用PARSER 来分配参数。试试这些,看看它们是否比完整的子解析器机制更适合您的需求。 【参考方案1】:

您的初始描述与子解析器非常接近,因此需要仔细阅读才能确定问题所在(对您而言)。

从 cmets 看来,最大的错误是子解析器捕获了 -h 给你一个帮助消息,而不是把它传递给 extras。子解析器,就像主解析器一样,采用add_help=False 参数。

p=argparse.ArgumentParser()
p.add_argument('foo')
p.add_argument('--bar')
sp=p.add_subparsers(dest='cmd')
sp1=sp.add_parser('cmd1')   # with a subparser help
sp2=sp.add_parser('cmd2', add_help=False)  # will ignore -h

生产

p.parse_known_args('-h'.split())   # top level help

p.parse_known_args('--bar xxx foo cmd1 -h'.split())
# usage: ipython foo cmd1 [-h]
# exit msg

p.parse_known_args('--bar xxx foo cmd2 -h'.split())
# (Namespace(bar='xxx', cmd='cmd2', foo='foo'), ['-h'])

p.parse_known_args('foo cmd2 test -o --more --bar xxx'.split())
# (Namespace(bar=None, cmd='cmd2', foo='foo'),
# ['test', '-o', '--more', '--bar', 'xxx'])

在评论中我提到了几个nargs 值,argparse.PARSERargparse.REMAINDER。对于主解析器,子解析器只是一个带有PARSER nargs(和choices). It's a specialaction` 类型的定位器,它会根据第一个值继续调用另一个解析器。

REMAINDER 类似于* nargs,除了它接受所有内容,甚至是看起来像标志的字符串。 PARSER 类似于+,至少需要一个字符串。

p=argparse.ArgumentParser()
p.add_argument('foo')
p.add_argument('--bar')
p.add_argument('rest', nargs=argparse.REMAINDER)

生产

In [32]: p.parse_args('--bar yyy foo'.split())
Out[32]: Namespace(bar='yyy', foo='foo', rest=[])

In [33]: p.parse_args('--bar yyy foo -h'.split())
Out[33]: Namespace(bar='yyy', foo='foo', rest=['-h'])

In [34]: p.parse_args('--bar yyy foo cmd2 test -o --more --bar xxx'.split())Out[34]: Namespace(bar='yyy', foo='foo', rest=['cmd2', 'test', '-o', '--more', '--bar', 'xxx'])

argparse 文档中的 REMAINDER 注释是:

argparse.REMAINDER。所有剩余的命令行参数都被收集到一个列表中。这对于调度到其他命令行实用程序的命令行实用程序通常很有用:

还有一个和我上一个类似的例子。

>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('--foo')
>>> parser.add_argument('command')
>>> parser.add_argument('args', nargs=argparse.REMAINDER)
>>> print(parser.parse_args('--foo B cmd --arg1 XX ZZ'.split()))
Namespace(args=['--arg1', 'XX', 'ZZ'], command='cmd', foo='B')

【讨论】:

【参考方案2】:

普通的sub-commands support in argparse 可以做到这一点。请注意,在重用参数名称时,您应该指定自定义 dest 以确保您的主要命令的值不会被覆盖:

parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='store_true')
subparsers = parser.add_subparsers()

parser_foo = subparsers.add_parser('foo')
parser_foo.add_argument('--foo', dest='foo_foo')
parser_foo.add_argument('--bar', dest='foo_bar')

parser_bar = subparsers.add_parser('bar')
parser_bar.add_argument('--foo', dest='bar_foo')

例子:

>>> parser.parse_args('-h'.split())
usage: [-h] [--foo] foo,bar ...

positional arguments:
  foo,bar

optional arguments:
  -h, --help  show this help message and exit
  --foo

>>> parser.parse_args('foo -h'.split())
usage:  foo [-h] [--foo FOO_FOO] [--bar FOO_BAR]

optional arguments:
  -h, --help     show this help message and exit
  --foo FOO_FOO
  --bar FOO_BAR

>>> parser.parse_args('bar -h'.split())
usage:  bar [-h] [--foo BAR_FOO]

optional arguments:
  -h, --help     show this help message and exit
  --foo BAR_FOO

>>> parser.parse_args('--foo foo --foo test --bar baz'.split())
Namespace(foo=True, foo_bar='baz', foo_foo='test')

【讨论】:

不幸的是,这不是我要问的。就我而言,我没有明确知道子命令的所有参数,我只是将它们传递给外部程序进行解释。我将编辑我的问题以使其更清楚。【参考方案3】:

arpgarse 模块具有子解析器功能,您可以将其与argparse.REMAINDER 结合使用以捕获任何参数而无需显式声明。

更新:

如果您想获得比argparse 提供的更多控制权,那么不妨研究一下click 包。它特别支持忽略未知选项并阻止处理像--help 这样的选项。详情在

http://click.pocoo.org/4/api/#click.Context.ignore_unknown_options

【讨论】:

不幸的是,这也不起作用。虽然它捕获未声明的参数,但它不会捕获已声明的参数,如“-h” - 因此如果在子命令之后传递“-h”而不是将“-h”传递给外部程序,argparse 将显示脚本帮助消息跨度>

以上是关于在子解析器之后停止解析的主要内容,如果未能解决你的问题,请参考以下文章

在子解析器 AWS AppSync 中获取父对象

JObject 解析器停止执行而不给出任何输出或错误

借用可以委派的解析器的检查器问题

如何在 GraphQL 突变/解析器中为每个用户创建和停止唯一的计时器

JavaScript 的 defer 与 async

solr中使用了什么类型的解析器