Python argparse 位置参数和子命令

Posted

技术标签:

【中文标题】Python argparse 位置参数和子命令【英文标题】:Python argparse positional arguments and sub-commands 【发布时间】:2012-01-29 21:50:55 【问题描述】:

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

这段代码运行良好:

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

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

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

上面的代码将 args 解析为 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

这是为什么?

【问题讨论】:

顺便说一句,似乎是known bug,已针对最近的 Python 版本进行了修复。 【参考方案1】:

起初我的想法和 jcollado 一样,但事实是,如果后续(***)位置参数具有特定的 nargsnargs = Nonenargs = 整数),然后它会按您的预期工作。当nargs'?''*' 时失败,有时为'+'。所以,我深入到代码中,想弄清楚发生了什么。

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

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

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

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

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

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

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

【讨论】:

请注意,当然,按照 jcollado 和 argparses 文档的建议,在所有顶层之后添加子解析器将打破歧义并按预期工作!【参考方案2】:
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),而不是位置参数。

【讨论】:

如果子解析器有位置参数怎么办?我们怎样才能让print(parser.parse_args(['subpositional', 'subparserarg'])) 打印:# -> Namespace(positional=None)?这应该说我们正在选择子命令“subpositional”,参数positional 是可选的。这可能吗?【参考方案3】:

我认为问题是在调用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

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

【讨论】:

不幸的是,它似乎不起作用。帮助确实看起来应该如此,但在实践中 - 它不会改变解析过程。【参考方案4】:

在 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

【讨论】:

以上是关于Python argparse 位置参数和子命令的主要内容,如果未能解决你的问题,请参考以下文章

python argparse详解

python-argparse.ArgumentParser()用法解析

Python argparse.ArgumentParser用法

python开发简单的命令行工具

Python 处理脚本的命令行参数:使用argparse

python argparse命名位置参数?