如何使用带有Python 2.7的Argparse模块设置默认子分析程序

时间:2017-10-10 13:20:21

标签: python-2.7 argparse subparsers

我正在使用Python 2.7,并且我正在尝试使用argparse来完成类似shell的行为。 我的问题一般来说,我似乎无法在Python 2.7中找到一种方法来使用argparse的subparsers作为可选项。 这很难解释我的问题所以我将描述我的程序需要什么。

该计划有两种工作模式:

  1. 使用给定命令启动程序(每个命令都有自己的命令) 附加参数)和其他参数将运行特定的 任务。
  2. 在没有命令的情况下启动程序将启动类似shell的程序,该程序可以接受一行参数并像处理它一样处理它们 程序是用给定的行作为参数调用的。
  3. 因此,如果我的程序支持'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(没有额外的args)或cmd2(带或不带-o标​​志)。对于两者,我可以添加标志-i来显示被调用命令的更多信息。

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

    限制:

    • 我不能使用Python 3(我知道它可以在那里轻松完成)
    • 由于全局可选参数,我无法检查是否没有参数来跳过arg解析。
    • 我不想添加新的命令来调用shell,它必须是在完全不提供命令时

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

2 个答案:

答案 0 :(得分:2)

关于'可选'主题的错误/问题(包含链接) subparsers。

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)

我认为&#39;可选&#39; subparsers影响Py2和3版本,但显然它没有。我必须查看代码以验证原因。

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

subparsers.required=True

(并在subparsers定义中添加dest),PY3错误消息为

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

因此,两个版本如何测试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的原因。

所以kludge就是编辑你的argparse.py并删除该块,使其与Py3版本的相应部分相匹配。

答案 1 :(得分:1)

另一个想法是使用2阶段解析。一个处理'全局',返回它无法处理的字符串。然后用subparsers有条件地处理额外的东西。

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$