2011-12-15 16 views
19

he el siguiente código (usando Python 2.7):subparser argparse opcional (para --version)

# 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() 

Ahora me gustaría ser capaz de llamar a este programa, por ejemplo, con el --version anexa al programa normal o alguna subcomando:

$ prog --version 
0.1 

$ prog db --version 
0.1 

Básicamente, tengo que declarar subparsers opcionales. Soy consciente de que esto no es really supported, pero ¿hay soluciones alternativas?

Editar: El mensaje de error que estoy recibiendo:

$ prog db --version 
# works fine 

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

Respuesta

17

Según la documentación, --version con action='version' (y no con action='store_true') imprime automáticamente el número de versión:

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

Sí, acabo marcó svn, que se utiliza como un ejemplo de objeto en el add_subparsers() documentation, y solo admite '--version' en el comando principal:

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

Still:

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

que produce:

python zacharyyoung$ ./arg-test.py --version 
arg-test.py 2.0 
python zacharyyoung$ ./arg-test.py foo --version 
arg-test.py foo 2.0 
1

Mientras que wait for this feature a ser liberados, podemos usar un código como éste:

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

Tenga en cuenta que esperamos esta característica básica desde 2009. – yac

+0

Empecé a usar docopt en lugar del analizador de argumentos incorporado. Admite el uso mixto con o sin "acciones", también conocidos como "verbos". http://docopt.org/ –

7

Fwiw, me encontré con esto también, y terminó "resolviéndolo" al no usar subparsers (ya tenía el mío propio sistema para imprimir ayuda, por lo que no perdió nada allí).

En su lugar, haga lo siguiente:

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

args, subcommand_args = parser.parse_known_args() 

... y luego el subcomando crea su propio analizador (similar a un subparser), que opera sólo en subcommand_args.

3

Como se discutió en http://bugs.python.org/issue9253 (argparse: subparsers opcionales), a partir de Python 3.3, los subparsers ahora son opcionales. Este fue un resultado involuntario de un cambio en la forma en que parse_args verificó los argumentos requeridos.

Encontré un fudge que restaura el comportamiento anterior (subparsers requeridos), estableciendo explícitamente el atributo required de la acción subparsers.

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() 

Consulte este tema para obtener más detalles. Espero que, si este problema se repare de forma adecuada, los subparsores se requieran de manera predeterminada, con algún tipo de opción para establecer su atributo required en False. Pero hay una gran acumulación de argparse parches.

0

Aunque la respuesta de @eumiro aborda la opción --version, solo puede hacerlo porque ese es un caso especial para optparse.Para permitir que las invocaciones generales de:

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

y tienen prog --version trabajo lo mismo que prog --verbose main (y prog main --verbose) se puede añadir un método para Argumentparser y llamar a que, con el nombre de la subparser defecto, justo antes de la invocación de parse_args():

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) 

El método set_default_subparser() es parte del paquete ruamel.std.argparse.

4

Esto parece implementar la idea básica de un subparser opcional. Analizamos los argumentos estándar que se aplican a todos los subcomandos. Entonces, si queda algo, invocamos al analizador en el resto. Los argumentos principales son un elemento primario del subcomando, por lo que -h aparece correctamente. Planeo ingresar una solicitud interactiva si no hay subcomandos presentes.

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)