Argumentos posicionales y subcomandos de Python argparse

Estoy trabajando con argparse y estoy tratando de mezclar subcomandos y argumentos posicionales, y surgió el siguiente problema.

Este código funciona bien:

import argparse parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() parser.add_argument('positional') subparsers.add_parser('subpositional') parser.parse_args('subpositional positional'.split()) 

El código anterior analiza los argumentos en el Namespace(positional='positional') , sin embargo, cuando cambio el argumento posicional para que tenga nargs = ‘?’ como tal:

 import argparse parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() parser.add_argument('positional', nargs='?') subparsers.add_parser('subpositional') parser.parse_args('subpositional positional'.split()) 

Se produce un error con:

 usage: [-h] {subpositional} ... [positional] : error: unrecognized arguments: positional 

¿Por qué es esto?

    Al principio pensé lo mismo que jcollado, pero luego está el hecho de que, si los argumentos posicionales subsiguientes (nivel superior) tienen un nargs específico ( nargs = None , nargs = integer), entonces funciona como usted espera. Falla cuando nargs es '?' o '*' , y algunas veces cuando es '+' . Entonces, bajé al código, para averiguar qué está pasando.

    Se reduce a la forma en que los argumentos se dividen para ser consumidos. Para averiguar quién obtiene qué, la llamada a parse_args resume los argumentos en una cadena como 'AA' , en su caso ( 'A' para argumentos posicionales, 'O' para opcional), y termina produciendo un patrón de parse_args para que parse_args con esa cadena de resumen, dependiendo de las acciones que haya agregado al analizador a través de los métodos .add_argument y .add_subparsers .

    En todos los casos, para su ejemplo, la cadena de argumento termina siendo 'AA' . Lo que cambia es el patrón que debe coincidir (puede ver los patrones posibles en _get_nargs_pattern en argparse.py . Para subpositional , termina siendo '(-*A[-AO]*)' , lo que significa que permite un argumento seguido de cualquier número de opciones o argumentos . Para la positional , depende del valor pasado a nargs :

    • None => '(-*A-*)'
    • 3 => '(-*A-*A-*A-*)' (uno '-*A' por argumento esperado)
    • '?' => '(-*A?-*)'
    • '*' => '(-*[A-]*)'
    • '+' => '(-*A[A-]*)'

    Esos patrones se adjuntan y, para nargs=None (su ejemplo de trabajo), termina con '(-*A[-AO]*)(-*A-*)' , que coincide con dos grupos ['A', 'A'] . De esta manera, subpositional solo analizará subpositional (lo que usted quería), mientras que positional coincidirá con su acción.

    Para nargs='?' , sin embargo, terminas con '(-*A[-AO]*)(-*A?-*)' . El segundo grupo está compuesto completamente de patrones opcionales , y * ser codicioso, significa que el primer grupo incluye todo en la cadena, terminando reconociendo a los dos grupos ['AA', ''] . Esto significa que la subpositional obtiene dos argumentos, y termina asfixiándose, por supuesto.

    nargs='+' , el patrón para nargs='+' es '(-*A[-AO]*)(-*A[A-]*)' , que funciona siempre que solo se pase un argumento . Diga subpositional a , ya que necesita al menos un argumento posicional en el segundo grupo. Nuevamente, como el primer grupo es codicioso, el hecho de pasar subpositional abcd te da ['AAAA', 'A'] , que no es lo que querías.

    En resumen: un desastre. Supongo que esto debería considerarse un error, pero no estoy seguro de cuál sería el impacto si los patrones se convirtieran en no codiciosos …

     import argparse parser = argparse.ArgumentParser() parser.add_argument('positional', nargs='?') subparsers = parser.add_subparsers() subparsers.add_parser('subpositional') print(parser.parse_args(['positional', 'subpositional'])) # -> Namespace(positional='positional') print(parser.parse_args(['subpositional'])) # -> Namespace(positional=None) parser.print_usage() # -> usage: bpython [-h] [positional] {subpositional} ... 

    La práctica común es que los argumentos antes del comando (en el lado izquierdo) pertenezcan al progtwig principal, después (a la derecha) – al comando. Por lo tanto, la positional debe ir antes que el comando subpositional . Progtwigs de ejemplo: git , twistd .

    Además un argumento con narg=? probablemente debería ser una opción ( --opt=value ), y no un argumento posicional.

    Creo que el problema es que cuando se llama a add_subparsers , se agrega un nuevo parámetro al analizador original para pasar el nombre del subparser.

    Por ejemplo, con este código:

     import argparse parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() parser.add_argument('positional') subparsers.add_parser('subpositional') parser.parse_args() 

    Obtienes la siguiente cadena de ayuda:

     usage: test.py [-h] {subpositional} ... positional positional arguments: {subpositional} positional optional arguments: -h, --help show this help message and exit 

    Tenga en cuenta que subpositional se muestra antes de la positional . Yo diría que lo que está buscando es tener el argumento posicional antes del nombre del subparser. Por lo tanto, probablemente lo que está buscando es agregar el argumento antes de los subparsers:

     import argparse parser = argparse.ArgumentParser() parser.add_argument('positional') subparsers = parser.add_subparsers() subparsers.add_parser('subpositional') parser.parse_args() 

    La cadena de ayuda obtenida con este código es:

     usage: test.py [-h] positional {subpositional} ... positional arguments: positional {subpositional} optional arguments: -h, --help show this help message and exit 

    De esta manera, usted pasa primero los argumentos al analizador principal, luego el nombre del subparser y finalmente los argumentos al subparser (si hay alguno).

    Sigue siendo un desastre en Python 3.5.

    Sugiero a SubClass ArgumentParser para mantener todos los argumentos posicionales restantes, y tratarlos más tarde:

     import argparse class myArgumentParser(argparse.ArgumentParser): def parse_args(self, args=None, namespace=None): args, argv = self.parse_known_args(args, namespace) args.remaining_positionnals = argv return args parser = myArgumentParser() options = parser.parse_args() 

    Los argumentos posicionales restantes están en la lista options.remaining_positionals