Python argparse 位置参数和子命令

发布于 2024-12-23 16:54:47 字数 798 浏览 1 评论 0原文

我正在使用 argparse 并尝试混合子命令和位置参数,出现了以下问题。

此代码运行良好:

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser.add_argument('positional')
subparsers.add_parser('subpositional')

parser.parse_args('subpositional positional'.split())

上面的代码将参数解析为 Namespace(positional='positional'),但是当我将位置参数更改为 nargs='?' 时因此:

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser.add_argument('positional', nargs='?')
subparsers.add_parser('subpositional')

parser.parse_args('subpositional positional'.split())

它会出错:

usage: [-h] {subpositional} ... [positional]
: error: unrecognized arguments: positional

这是为什么?

I'm working with argparse and am trying to mix sub-commands and positional arguments, and the following issue came up.

This code runs fine:

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser.add_argument('positional')
subparsers.add_parser('subpositional')

parser.parse_args('subpositional positional'.split())

The above code parses the args into Namespace(positional='positional'), however when I change the positional argument to have nargs='?' as such:

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser.add_argument('positional', nargs='?')
subparsers.add_parser('subpositional')

parser.parse_args('subpositional positional'.split())

It errors out with:

usage: [-h] {subpositional} ... [positional]
: error: unrecognized arguments: positional

Why is this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

春花秋月 2024-12-30 16:54:47

起初我认为与 jcollado 相同,但事实是,如果后续(顶级)位置参数具有特定的 nargs (nargs = None, nargs = 整数),那么它就会按您的预期工作。当 nargs'?''*' 时失败,有时当它为 '+' 时失败。因此,我深入研究代码,弄清楚发生了什么。

它归结为参数被分割以供使用的方式。为了弄清楚谁得到了什么,对 parse_args 的调用将参数总结为 'AA' 等字符串,在您的情况下 ('A'对于位置参数,'O' 表示可选),最终生成一个与该摘要字符串匹配的正则表达式模式,具体取决于您通过 添加到解析器的操作。 add_argument.add_subparsers 方法。

在每种情况下,例如,参数字符串最终都是 'AA'。改变的是要匹配的模式(您可以在 argparse.py 中的 _get_nargs_pattern 下看到可能的模式。对于 subpositional ,它最终是 < code>'(-*A[-AO]*)',这意味着允许一个参数后跟任意数量的选项或参数对于位置,这取决于传递给的值nargs

  • => '(-*A-*)'
  • 3 => '(-*A-* A-*A-*)'(每个预期参数一个 '-*A'
  • '?' => '(-* A?-*)'
  • '*' => '(-*[A-]*)'
  • '+' => '(-*A[A-]*)'< /code>

这些模式被附加,对于 nargs=None (您的工作示例),您最终会得到 '(-*A[-AO]*)(-*A-* )',匹配两个组['A', 'A'] 这样,subpositional 将仅解析 subpositional (您想要的),而 positional 将匹配其操作。

但是,对于 nargs='?',您最终会得到 '(-*A[-AO]*)(-*A?-*)'。第二组完全由可选模式组成,并且*是贪婪的,这意味着第一组通配字符串中的所有内容,最终识别出两个组[ 'AA','']。这意味着 subpositional 有两个参数,当然最终会令人窒息。

有趣的是,nargs='+' 的模式是 '(-*A[-AO]*)(-*A[A-]*)',其中只要你只传递一个参数就可以工作。说subpositional a,因为您需要在第二组中至少有一个位置参数。同样,由于第一组是贪婪的,因此传递 subpositional abc d 会得到 ['AAAA', 'A'],这不是您想要的。

简而言之:一团糟。我想这应该被认为是一个错误,但不确定如果模式变成非贪婪模式会产生什么影响......

At first I thought the same as jcollado, but then there's the fact that, if the subsequent (top level) positional arguments have a specific nargs (nargs = None, nargs = integer), then it works as you expect. It fails when nargs is '?' or '*', and sometimes when it is '+'. So, I went down to the code, to figure out what is going on.

It boils down to the way the arguments are split to be consumed. To figure out who gets what, the call to parse_args summarizes the arguments in a string like 'AA', in your case ('A' for positional arguments, 'O' for optional), and ends up producing a regex pattern to be matched with that summary string, depending on the actions you've added to the parser through the .add_argument and .add_subparsers methods.

In every case, for you example, the argument string ends up being 'AA'. What changes is the pattern to be matched (you can see the possible patterns under _get_nargs_pattern in argparse.py. For subpositional it ends up being '(-*A[-AO]*)', which means allow one argument followed by any number of options or arguments. For positional, it depends on the value passed to nargs:

  • None => '(-*A-*)'
  • 3 => '(-*A-*A-*A-*)' (one '-*A' per expected argument)
  • '?' => '(-*A?-*)'
  • '*' => '(-*[A-]*)'
  • '+' => '(-*A[A-]*)'

Those patterns are appended and, for nargs=None (your working example), you end up with '(-*A[-AO]*)(-*A-*)', which matches two groups ['A', 'A']. This way, subpositional will parse only subpositional (what you wanted), while positional will match its action.

For nargs='?', though, you end up with '(-*A[-AO]*)(-*A?-*)'. The second group is comprised entirely of optional patterns, and * being greedy, that means the first group globs everything in the string, ending up recognizing the two groups ['AA', '']. This means subpositional gets two arguments, and ends up choking, of course.

Funny enough, the pattern for nargs='+' is '(-*A[-AO]*)(-*A[A-]*)', which works as long as you only pass one argument. Say subpositional a, as you require at least one positional argument in the second group. Again, as the first group is greedy, passing subpositional a b c d gets you ['AAAA', 'A'], which is not what you wanted.

In brief: a mess. I guess this should be considered a bug, but not sure what the impact would be if the patterns are turned into non-greedy ones...

吻泪 2024-12-30 16:54:47
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional', nargs='?')

subparsers = parser.add_subparsers()
subparsers.add_parser('subpositional')

print(parser.parse_args(['positional', 'subpositional']))
# -> Namespace(positional='positional')
print(parser.parse_args(['subpositional']))
# -> Namespace(positional=None)
parser.print_usage()
# -> usage: bpython [-h] [positional] {subpositional} ...

常见的做法是命令之前(左侧)的参数属于主程序,命令之后(右侧)的参数属于命令。因此,positional 应该位于命令 subpositional 之前。示例程序:gittwistd

此外,带有 narg=? 的参数可能应该是一个选项 (--opt=value),而不是位置参数。

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional', nargs='?')

subparsers = parser.add_subparsers()
subparsers.add_parser('subpositional')

print(parser.parse_args(['positional', 'subpositional']))
# -> Namespace(positional='positional')
print(parser.parse_args(['subpositional']))
# -> Namespace(positional=None)
parser.print_usage()
# -> usage: bpython [-h] [positional] {subpositional} ...

The common practice is that arguments before the command (on the left side) belong to the main program, after (on the right) -- to the command. Therefore positional should go before the command subpositional. Example programs: git, twistd.

Additionally an argument with narg=? should probably be an option (--opt=value), and not a positional argument.

北风几吹夏 2024-12-30 16:54:47

我认为问题在于,当调用 add_subparsers 时,会向原始解析器添加一个新参数以传递子解析器的名称。

例如,使用以下代码:

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser.add_argument('positional')                                             
subparsers.add_parser('subpositional')                                             

parser.parse_args()

您将获得以下帮助字符串:

usage: test.py [-h] {subpositional} ... positional

positional arguments:
  {subpositional}
  positional

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

请注意,subpositional 显示在 positional 之前。我想说你要寻找的是在子解析器名称之前有位置参数。因此,您可能正在寻找的是在子解析器之前添加参数:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional')

subparsers = parser.add_subparsers()
subparsers.add_parser('subpositional')

parser.parse_args()

使用此代码获得的帮助字符串是:

usage: test.py [-h] positional {subpositional} ...

positional arguments:
  positional
  {subpositional}

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

这样,您首先将参数传递给主解析器,然后传递子解析器的名称,最后传递给主解析器的参数子解析器(如果有)。

I think that the problem is that when add_subparsers is called, a new parameter is added to the original parser to pass the name of the subparser.

For example, with this code:

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser.add_argument('positional')                                             
subparsers.add_parser('subpositional')                                             

parser.parse_args()

You get the following help string:

usage: test.py [-h] {subpositional} ... positional

positional arguments:
  {subpositional}
  positional

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

Note that subpositional is displayed before positional. I'd say that what you're looking for is to have the positional argument before the subparser name. Hence, probably what you're looking for is adding the argument before the subparsers:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('positional')

subparsers = parser.add_subparsers()
subparsers.add_parser('subpositional')

parser.parse_args()

The help string obtained with this code is:

usage: test.py [-h] positional {subpositional} ...

positional arguments:
  positional
  {subpositional}

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

This way, you pass first the arguments to the main parser, then the name of the subparser and finally the arguments to the subparser (if any).

善良天后 2024-12-30 16:54:47

Python 3.5 中仍然是一团糟。

我建议子类 ArgumentParser 保留所有剩余的位置参数,并稍后处理它们:

import argparse

class myArgumentParser(argparse.ArgumentParser):
    def parse_args(self, args=None, namespace=None):
       args, argv = self.parse_known_args(args, namespace)
       args.remaining_positionnals = argv
       return args

parser = myArgumentParser()

options = parser.parse_args()

剩余的位置参数在列表 options.remaining_positionals

It's still a mess in Python 3.5.

I suggest to subClass ArgumentParser to keep all the remaining positional arguments, and to deal with them later:

import argparse

class myArgumentParser(argparse.ArgumentParser):
    def parse_args(self, args=None, namespace=None):
       args, argv = self.parse_known_args(args, namespace)
       args.remaining_positionnals = argv
       return args

parser = myArgumentParser()

options = parser.parse_args()

The remaining positional arguments are in the list options.remaining_positionals

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文