【问题标题】:argparse optional subparser (for --version)argparse 可选子解析器(用于 --version)
【发布时间】:2012-01-21 05:43:49
【问题描述】:

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

# shared command line options, like --version or --verbose
parser_shared = argparse.ArgumentParser(add_help=False)
parser_shared.add_argument('--version', action='store_true')

# the main parser, inherits from `parser_shared`
parser = argparse.ArgumentParser(description='main', parents=[parser_shared])

# several subcommands, which can't inherit from the main parser, since
# it would expect subcommands ad infinitum
subparsers = parser.add_subparsers('db', parents=[parser_shared])

...

args = parser.parse_args()

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

$ prog --version
0.1

$ prog db --version
0.1

基本上,我需要声明可选的子解析器。我知道这不是really supported,但有任何解决方法或替代方法吗?

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

$ prog db --version
# works fine

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

【问题讨论】:

    标签: python command-line-interface argparse subcommand


    【解决方案1】:

    根据文档,--versionaction='version'(而不是 action='store_true')会自动打印版本号:

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

    【讨论】:

      【解决方案2】:

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

      相反,我这样做:

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

      ...然后子命令创建它自己的解析器(类似于子解析器),它只在subcommand_args 上运行。

      【讨论】:

        【解决方案3】:

        这似乎实现了可选子解析器的基本思想。我们解析适用于所有子命令的标准参数。然后,如果剩下任何东西,我们就调用其余部分的解析器。主要参数是子命令的父级,因此 -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 )
        

        【讨论】:

          【解决方案4】:

          正如http://bugs.python.org/issue9253(argparse:可选子解析器)中所讨论的,从 Python 3.3 开始,子解析器现在是可选的。这是 parse_args 检查所需参数的方式发生变化的意外结果。

          我发现了一个可以恢复以前(必需的子解析器)行为的软糖,显式设置subparsers 操作的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()
          

          有关详细信息,请参阅该问题。我希望如果这个问题得到正确修补,默认情况下将需要子解析器,并通过某种选项将其required 属性设置为False。但是有大量的 argparse 补丁积压。

          【讨论】:

            【解决方案5】:

            是的,我刚查了svn,它在add_subparsers() documentation中用作对象示例,它只支持主命令上的'--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
            

            【讨论】:

              【解决方案6】:

              当我们wait for this feature 被交付时,我们可以使用这样的代码:

              # 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:]
              

              【讨论】:

              • 请注意,我们从 2009 年开始等待这个基本功能。
              • 我已经开始使用 docopt 而不是内置的参数解析器。它确实支持有或没有“动作”的混合使用,也就是“动词”。 docopt.org
              【解决方案7】:

              虽然@eumiro 的回答解决了--version 选项,但它只能这样做,因为这是 optparse 的一种特殊情况。允许一般调用:

               prog
               prog --verbose
               prog --verbose main
               prog --verbose db 
              

              并让prog --versionprog --verbose main(和prog main --verbose)工作相同,您可以在调用parse_args() 之前向Argumentparser 添加一个方法并使用默认子解析器的名称调用它:

              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)
              

              set_default_subparser() 方法是 ruamel.std.argparse 包的一部分。

              【讨论】:

                猜你喜欢
                • 2013-12-21
                • 2020-09-26
                • 2013-10-09
                • 1970-01-01
                • 1970-01-01
                • 2018-12-31
                • 1970-01-01
                • 2015-07-17
                相关资源
                最近更新 更多