2011-12-29 14 views
11

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

Este código funciona muy 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 Namespace(positional='positional'), sin embargo cuando cambie el argumento posicional tener nargs = '?' tales como:

import argparse 
parser = argparse.ArgumentParser() 
subparsers = parser.add_subparsers() 

parser.add_argument('positional', nargs='?') 
subparsers.add_parser('subpositional') 

parser.parse_args('subpositional positional'.split()) 

él los errores hacia fuera con:

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

¿Por qué es esto?

+0

Por cierto, parece un [fallo conocido ] (http://bugs.python.org/issue9340), que se ha corregido para las versiones recientes de Python. –

Respuesta

9

Al principio pensé lo mismo que jcollado, pero luego está el hecho de que, si el (primer nivel) argumentos posicionales subsiguientes tienen una específica nargs (nargs = None, nargs = entero), entonces funciona como se espera. Falla cuando nargs es '?' o '*', y algunas veces cuando es '+'. Entonces, fui al código, para descubrir qué está pasando.

Todo se reduce a la forma en que los argumentos se dividen para ser consumidos. Para averiguar quién recibe qué, la llamada al 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 expresiones regulares para coincidir con esa cadena de resumen, dependiendo sobre las acciones que ha 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 se emparejará (puede ver los patrones posibles en _get_nargs_pattern en argparse.py. Para subpositional termina siendo '(-*A[-AO]*)', lo que significa permite un argumento seguido de cualquier número de opciones o argumentos.Para positional, que depende del valor pasado a nargs:

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

Esos patrones se anexan y, por nargs=None (el ejemplo de trabajo), que terminan con '(-*A[-AO]*)(-*A-*)', que coincide con dos grupos ['A', 'A']. De esta forma, subpositional analizará solo subpositional (lo que deseaba), mientras que positional coincidiría con su acción.

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

divertido lo suficiente, el patrón de nargs='+' es '(-*A[-AO]*)(-*A[A-]*)', que trabaja , siempre y cuando sólo se pasa un argumento. Diga subpositional a, ya que necesita al menos un argumento posicional en el segundo grupo. De nuevo, como el primer grupo es codicioso, al pasar subpositional a b c d obtienes ['AAAA', 'A'], que no es lo que querías.

En resumen: un desastre. Creo que esto debe ser considerado un error, pero no está seguro de cuál sería el impacto si los patrones se convierten en los no codiciosos ...

+0

Tenga en cuenta que, por supuesto, agregar los subparsores después de todo el nivel superior, según lo sugerido por la documentación de jcollado y argparses, ¡romperá la ambigüedad y funcionará según lo previsto! –

5

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

Se obtiene la siguiente ayuda cadena:

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 positional. Diría que lo que estás buscando es tener el argumento posicional antes del nombre del subparser. Por lo tanto, probablemente lo que usted está buscando es la adición de la discusión 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 obtenido 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, se pasa en primer lugar los argumentos de la analizador principal, luego el nombre del subparser y finalmente los argumentos para el subparser (si hay alguno).

+0

Desafortunadamente no parece funcionar. La ayuda realmente se ve como debería, pero en la práctica, no cambia el proceso de análisis sintáctico. – kcpr

6
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 de la orden (a la izquierda lado) pertenecen al programa principal, después (a la derecha) - al comando. Por lo tanto, positional debe ir antes del comando subpositional. Programas de ejemplo: git, twistd.

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

+0

¿Qué sucede si el subparser tenía argumentos posicionales? ¿Cómo podemos '' print (parser.parse_args (['subpositional', 'subparserarg'])) 'print:' # -> Namespace (positional = None) '? Esto debería haber dicho que estamos seleccionando el subcomando 'subpositional' con el argumento 'positional' como opcional. es posible? – jmlopez

0

Todavía es un desastre en Python 3.5.

que sugieren que la subclase ArgumentParser de mantener todos los argumentos posicionales restantes, y tratar con ellos más adelante:

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

Cuestiones relacionadas