【问题标题】:How to Set a Default Subparser using Argparse Module with Python 2.7如何在 Python 2.7 中使用 Argparse 模块设置默认子解析器
【发布时间】:2018-03-21 22:13:27
【问题描述】:

我正在使用 Python 2.7,并且正在尝试使用 argparse 完成类似 shell 的行为。 我的问题,一般来说,我似乎无法在 Python 2.7 中找到一种方法来使用 argparse 的子解析器作为可选。 解释我的问题有点困难,所以我将描述我对程序的要求。

该程序有两种工作模式:

  1. 使用给定命令启动程序(每个命令都有自己的 附加参数)和附加参数将运行特定的 任务。
  2. 在没有命令的情况下启动程序将启动一个类似于 shell 的程序,该程序可以接受一行参数并处理它们,就像 程序是用给定的行作为它的参数调用的。

所以,例如,如果我的程序支持“cmd1”和“cmd2”命令,我可以这样使用它:

  • python program.py cmd1 additional_args1
  • python program.py cmd2 additional_args2

或使用 shell 模式:

  • python program.py
    • cmd1 additional_args1
    • cmd2 additional_args2
    • quit

此外,我还希望我的程序能够采用将影响所有命令的可选全局参数。

为此我使用 argparse 像这样(这是一个纯粹的例子):

parser = argparse.ArgumentParser(description="{} - Version {}".format(PROGRAM_NAME, PROGRAM_VERSION))

parser.add_argument("-i", "--info",  help="Display more information")

subparsers = parser.add_subparsers()

parserCmd1 = subparsers.add_parser("cmd1", help="First Command")
parserCmd1.set_defaults(func=cmd1)

parserCmd2 = subparsers.add_parser("cmd2", help="Second Command")
parserCmd2.add_argument("-o", "--output", help="Redirect Output")
parserCmd2.set_defaults(func=cmd2)

所以我可以调用 cmd1(没有额外的参数)或 cmd2(有或没有 -o 标志)。对于两者,我都可以添加标志 -i 以显示被调用命令的更多信息。

我的问题是我无法激活 shell 模式,因为我必须提供 cmd1 或 cmd2 作为参数(因为使用了必需的子解析器)

限制:

  • 我不能使用 Python 3(我知道在那里很容易做到)
  • 由于全局可选参数,我无法检查是否没有参数可以跳过 arg 解析。
  • 我不想添加一个新的命令来调用shell,它必须是在根本不提供任何命令时

那么如何使用 argparse 和 python 2.7 实现这种行为呢?

【问题讨论】:

  • 只是一个简短的说明 - 子解析器是可选的这一事实是一个错误。它们曾经是必需的(就像正常的位置一样),但在几年前的一次不相关的变化中,子解析器从裂缝中消失了。我将不得不更详细地研究您的问题,以了解您为什么认为 Py2 和 Py3 在这方面存在差异。

标签: python-2.7 argparse subparsers


【解决方案1】:

关于“可选”子解析器主题的错误/问题(带有链接)。

https://bugs.python.org/issue29298

请注意,这有一个最近的拉取请求。


随着你的脚本和添加

args = parser.parse_args()
print(args)

结果是

1008:~/mypy$ python3 stack46667843.py 
Namespace(info=None)
1009:~/mypy$ python2 stack46667843.py 
usage: stack46667843.py [-h] [-i INFO] {cmd1,cmd2} ...
stack46667843.py: error: too few arguments
1009:~/mypy$ python2 stack46667843.py cmd1
Namespace(func=<function cmd1 at 0xb748825c>, info=None)
1011:~/mypy$ python3 stack46667843.py cmd1
Namespace(func=<function cmd1 at 0xb7134dac>, info=None)

我认为“可选”子解析器同时影响了 Py2 和 3 版本,但显然没有。我将不得不查看代码以验证原因。


在两种语言中,subparsers.requiredFalse。如果我将其设置为 true

subparsers.required=True

(并在子解析器定义中添加dest),PY3 错误信息为

1031:~/mypy$ python3 stack46667843.py
usage: stack46667843.py [-h] [-i INFO] {cmd1,cmd2} ...
stack46667843.py: error: the following arguments are required: cmd

因此,这 2 个版本对 required 参数的测试方式有所不同。 Py3注意required属性; Py2(显然)使用早期的方法来检查positionals 列表是否为空。


parser._parse_known_args 的末尾附近检查所需的参数。

Python2.7包含

    # if we didn't use all the Positional objects, there were too few
    # arg strings supplied.
    if positionals:
        self.error(_('too few arguments'))

在检查action.required 的迭代之前。这就是捕捉丢失的cmd 和说too few arguments 的原因。

因此需要编辑您的 argparse.py 并删除该块,使其与 Py3 版本的相应部分匹配。

【讨论】:

  • 您好,感谢您的回复。我也无法编辑 argparse,出于同样的原因,我无法使用 Python3。这将成为我所在公司的人员使用的工具。我唯一的保证是他们安装了 Python 2.7。
【解决方案2】:

另一个想法是使用 2 阶段解析。一个处理“全局变量”,返回它无法处理的字符串。然后使用子解析器有条件地处理额外的内容。

import argparse

def cmd1(args):
    print('cmd1', args)
def cmd2(args):
    print('cmd2', args)

parser1 = argparse.ArgumentParser()

parser1.add_argument("-i", "--info",  help="Display more information")

parser2 = argparse.ArgumentParser()
subparsers = parser2.add_subparsers(dest='cmd')

parserCmd1 = subparsers.add_parser("cmd1", help="First Command")
parserCmd1.set_defaults(func=cmd1)

parserCmd2 = subparsers.add_parser("cmd2", help="Second Command")
parserCmd2.add_argument("-o", "--output", help="Redirect Output")
parserCmd2.set_defaults(func=cmd2)

args, extras = parser1.parse_known_args()
if len(extras)>0 and extras[0] in ['cmd1','cmd2']:
    args = parser2.parse_args(extras, namespace=args)
    args.func(args)
else:
    print('doing system with', args, extras)

样本运行:

0901:~/mypy$ python stack46667843.py -i info
('doing system with', Namespace(info='info'), [])
0901:~/mypy$ python stack46667843.py -i info extras for sys
('doing system with', Namespace(info='info'), ['extras', 'for', 'sys'])
0901:~/mypy$ python stack46667843.py -i info cmd1
('cmd1', Namespace(cmd='cmd1', func=<function cmd1 at 0xb74b025c>, info='info'))
0901:~/mypy$ python stack46667843.py -i info cmd2 -o out
('cmd2', Namespace(cmd='cmd2', func=<function cmd2 at 0xb719ebc4>, info='info', output='out'))
0901:~/mypy$ 

【讨论】:

  • 感谢您的回复,我能够接受您的回答并实施我所需要的。
猜你喜欢
  • 2014-08-31
  • 2014-06-19
  • 2017-09-27
  • 2011-11-20
  • 2017-02-21
  • 2012-03-11
  • 2018-12-31
  • 2016-06-04
  • 2018-04-08
相关资源
最近更新 更多