Python argparse可选子parser(用于--version)

Python argparse可选子parser(用于--version),python,command-line-interface,argparse,subcommand,Python,Command Line Interface,Argparse,Subcommand,我有以下代码(使用Python 2.7): 现在,我希望能够调用此程序,例如,将--version附加到普通程序或某个子命令后: $ prog --version 0.1 $ prog db --version 0.1 基本上,我需要声明可选的子parser。我知道这不是,但有什么解决办法或替代方案吗 编辑:我收到的错误消息: $ prog db --version # works fine $ prog --version usage: .... prog: error: too few

我有以下代码(使用Python 2.7):

现在,我希望能够调用此程序,例如,将
--version
附加到普通程序或某个子命令后:

$ prog --version
0.1

$ prog db --version
0.1
基本上,我需要声明可选的子parser。我知道这不是,但有什么解决办法或替代方案吗

编辑:我收到的错误消息:

$ prog db --version
# works fine

$ prog --version
usage: ....
prog: error: too few arguments

根据文档,
--version
with
action='version'
(而不是with
action='store\u true'
)会自动打印版本号:

parser.add_argument('--version', action='version', version='%(prog)s 2.0')

是的,我刚刚检查了
svn
,它在中用作对象示例,并且它只支持主命令上的“-version”:

python zacharyyoung$ svn log --version
Subcommand 'log' doesn't accept option '--version'
Type 'svn help log' for usage.
仍然:

# create common parser
parent_parser = argparse.ArgumentParser('parent', add_help=False)
parent_parser.add_argument('--version', action='version', version='%(prog)s 2.0')

# create the top-level parser
parser = argparse.ArgumentParser(parents=[parent_parser])
subparsers = parser.add_subparsers()

# create the parser for the "foo" command
parser_foo = subparsers.add_parser('foo', parents=[parent_parser])
这将产生:

python zacharyyoung$ ./arg-test.py --version
arg-test.py 2.0
python zacharyyoung$ ./arg-test.py foo --version
arg-test.py foo 2.0
当我们需要交付时,我们可以使用如下代码:

# Make sure that main is the default sub-parser
if '-h' not in sys.argv and '--help' not in sys.argv:
    if len(sys.argv) < 2:
        sys.argv.append('main')
    if sys.argv[1] not in ('main', 'test'):
        sys.argv = [sys.argv[0], 'main'] + sys.argv[1:]
#确保main是默认的子解析器
如果'-h'不在sys.argv中,并且'-help'不在sys.argv中:
如果len(系统argv)<2:
sys.argv.append('main')
如果sys.argv[1]不在('main','test'):
sys.argv=[sys.argv[0],'main']+sys.argv[1:]

FWIW,我也遇到了这个问题,最终通过不使用子Parser“解决”了它(我已经有了自己的打印帮助系统,所以没有丢失任何东西)

相反,我这样做:

parser.add_argument("command", nargs="?",
                    help="name of command to execute")

args, subcommand_args = parser.parse_known_args()
…然后子命令创建自己的解析器(类似于子parser),该解析器仅在
子命令_args

上运行,如(argparse:optional subparser)中所述,从Python 3.3开始,子parser现在是可选的。这是由于
parse_args
检查所需参数的方式发生变化而导致的意外结果

我发现了一个修复以前(必需的子parser)行为的方法,显式地设置了
subparser
操作的
required
属性

parser = ArgumentParser(prog='test')
subparsers = parser.add_subparsers()
subparsers.required = True   # the fudge
subparsers.dest = 'command'
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()

有关更多详细信息,请参阅该问题。我预计,如果此问题得到正确修补,默认情况下将需要子parser,并提供某种选项将其
required
属性设置为
False
。但是有大量的
argparse
补丁积压。

尽管@eumiro的答案解决了
--version
选项,但它只能这样做,因为这是optparse的特例。要允许一般调用:

 prog
 prog --verbose
 prog --verbose main
 prog --verbose db 
prog--version
prog--verbose main
(和
prog main--verbose
)的工作方式相同,您可以向Argumentparser添加一个方法,并在调用
parse_args()
之前使用默认子parser的名称调用该方法:


set\u default\u subparser()
方法是包的一部分。

这似乎实现了可选subparser的基本思想。我们解析适用于所有子命令的标准参数。然后,如果剩下什么,我们调用其余部分的解析器。主参数是子命令的父参数,因此-h正确显示。如果没有子命令,我计划输入一个交互式提示

import argparse

p1 = argparse.ArgumentParser( add_help = False )    
p1.add_argument( ‘–flag1′ )

p2 = argparse.ArgumentParser( parents = [ p1 ] )
s = p2.add_subparsers()
p = s.add_parser( ‘group’ )
p.set_defaults( group=True )

( init_ns, remaining ) = p1.parse_known_args( )

if remaining:
    p2.parse_args( args = remaining, namespace=init_ns )
else:
    print( ‘Enter interactive loop’ )

print( init_ns )

请注意,我们从2009年就开始等待这个基本特性了。我已经开始使用docopt而不是内置参数解析器。它支持混合使用或不使用“动作”,即“动词”。
import argparse
import sys

def set_default_subparser(self, name, args=None):
    """default subparser selection. Call after setup, just before parse_args()
    name: is the name of the subparser to call by default
    args: if set is the argument list handed to parse_args()

    , tested with 2.7, 3.2, 3.3, 3.4
    it works with 2.6 assuming argparse is installed
    """
    subparser_found = False
    for arg in sys.argv[1:]:
        if arg in ['-h', '--help']:  # global help if no subparser
            break
    else:
        for x in self._subparsers._actions:
            if not isinstance(x, argparse._SubParsersAction):
                continue
            for sp_name in x._name_parser_map.keys():
                if sp_name in sys.argv[1:]:
                    subparser_found = True
        if not subparser_found:
            # insert default in first position, this implies no
            # global options without a sub_parsers specified
            if args is None:
                sys.argv.insert(1, name)
            else:
                args.insert(0, name)

argparse.ArgumentParser.set_default_subparser = set_default_subparser

def do_main(args):
    print 'main verbose', args.verbose

def do_db(args):
    print 'db verbose:', args.verbose

parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--version', action='version', version='%(prog)s 2.0')
subparsers = parser.add_subparsers()
sp = subparsers.add_parser('main')
sp.set_defaults(func=do_main)
sp.add_argument('--verbose', action='store_true')
sp = subparsers.add_parser('db')
sp.set_defaults(func=do_db)

parser.set_default_subparser('main')
args = parser.parse_args()

if hasattr(args, 'func'):
    args.func(args)
import argparse

p1 = argparse.ArgumentParser( add_help = False )    
p1.add_argument( ‘–flag1′ )

p2 = argparse.ArgumentParser( parents = [ p1 ] )
s = p2.add_subparsers()
p = s.add_parser( ‘group’ )
p.set_defaults( group=True )

( init_ns, remaining ) = p1.parse_known_args( )

if remaining:
    p2.parse_args( args = remaining, namespace=init_ns )
else:
    print( ‘Enter interactive loop’ )

print( init_ns )