2011-12-15 17 views
17

Tengo un script donde le pido al usuario una lista de acciones predefinidas para realizar. También quiero la capacidad de asumir una lista particular de acciones cuando el usuario no define nada. sin embargo, parece que tratar de hacer ambas cosas juntos es imposible.python argparse - opcional anexar argumento con elecciones

cuando el usuario da ningún argumento, que recibe un error que la opción por defecto no es válido

acts = ['clear','copy','dump','lock'] 
p = argparse.ArgumentParser() 
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']]) 
args = p.parse_args([]) 
>>> usage: [-h] [{clear,copy,dump,lock} [{clear,copy,dump,lock} ...]] 
: error: argument action: invalid choice: [['dump', 'clear']] (choose from 'clear', 'copy', 'dump', 'lock') 

y cuando lo hacen definir un conjunto de acciones, el espacio de nombres resultante tiene las acciones del usuario anexan al valor predeterminado, en lugar de sustituir el valor predeterminado

acts = ['clear','copy','dump','lock'] 
p = argparse.ArgumentParser() 
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']]) 
args = p.parse_args(['lock']) 
args 
>>> Namespace(action=[['dump', 'clear'], ['dump']]) 
+0

No estoy seguro de si esto se ha resuelto mediante un informe de error similar: http://bugs.python.org/issue9625. Una forma posible de manejar esto es usar una acción personalizada, en lugar de la palabra clave 'choices'. Ver la respuesta aceptada en [esta pregunta] (http://stackoverflow.com/questions/4194948/python-argparse-is-there-a-way-to-specify-a-range-in-nargs) – Chris

Respuesta

15

Lo que necesitamos pueden ser hecho usando un personalizado argparse.Action como en el siguiente ejemplo:

import argparse 

parser = argparse.ArgumentParser() 

class DefaultListAction(argparse.Action): 
    CHOICES = ['clear','copy','dump','lock'] 
    def __call__(self, parser, namespace, values, option_string=None): 
     if values: 
      for value in values: 
       if value not in self.CHOICES: 
        message = ("invalid choice: {0!r} (choose from {1})" 
           .format(value, 
             ', '.join([repr(action) 
                for action in self.CHOICES]))) 

        raise argparse.ArgumentError(self, message) 
      setattr(namespace, self.dest, values) 

parser.add_argument('actions', nargs='*', action=DefaultListAction, 
        default = ['dump', 'clear'], 
        metavar='ACTION') 

print parser.parse_args([]) 
print parser.parse_args(['lock']) 

la salida de la secuencia de comandos es:

$ python test.py 
Namespace(actions=['dump', 'clear']) 
Namespace(actions=['lock']) 
1

la acción estaba siendo anexa a causa de la "acción = 'append'" parámetro que pasan a argparse.

Después de eliminar este parámetro, los argumentos que pasa un usuario se mostrarán solos, pero el programa generará un error cuando no se pasen los argumentos.

Agregar un prefijo '-' al primer parámetro lo resuelve de la manera más lenta.

acts = ['clear','copy','dump','lock'] 
p = argparse.ArgumentParser() 
p.add_argument('--action', nargs='*', choices=acts, default=[['dump', 'clear']]) 
args = p.parse_args() 

La desventaja de este enfoque es que las opciones introducidas por el usuario ahora deben estar precedidos por '--action', como:

app.py --action clear dump copy 
3

Se puede comprobar si el usuario está suministrando acciones (en cuyo caso analizar como se requiere, argumento posición), o está suministrando hay acciones (en cuyo caso analiza como un argumento opcional con predeterminado):

import argparse 
import sys 

acts = ['clear', 'copy', 'dump', 'lock'] 
p = argparse.ArgumentParser() 
if sys.argv[1:]: 
    p.add_argument('action', nargs = '*', choices = acts) 
else: 
    p.add_argument('--action', default = ['dump', 'clear']) 

args = p.parse_args() 
print(args) 

cuando se ejecuta, los rendimientos de estos resultados:

% test.py 
Namespace(action=['dump', 'clear']) 
% test.py lock 
Namespace(action=['lock']) 
% test.py lock dump 
Namespace(action=['lock', 'dump']) 

es probable que haya otras opciones para analizar también. En ese caso, podría utilizar parse_known_args para analizar las otras opciones, y luego manejar las unknown argumentos en un segundo paso:

import argparse 

acts = ['clear', 'copy', 'dump', 'lock'] 
p = argparse.ArgumentParser() 
p.add_argument('--foo') 
args, unknown = p.parse_known_args() 
if unknown: 
    p.add_argument('action', nargs = '*', choices = acts) 
else: 
    p.add_argument('--action', default = ['dump', 'clear']) 

p.parse_args(unknown, namespace = args) 
print(args) 

cuando se ejecuta, produce estos resultados:

% test.py 
Namespace(action=['dump', 'clear'], foo=None) 
% test.py --foo bar 
Namespace(action=['dump', 'clear'], foo='bar') 
% test.py lock dump 
Namespace(action=['lock', 'dump'], foo=None) 
% test.py lock dump --foo bar 
Namespace(action=['lock', 'dump'], foo='bar') 
4

En la documentación (http://docs.python.org/dev/library/argparse.html#default), se dice:

Para argumentos posicionales con nargs igual a? o *, el valor predeterminado se usa cuando no hay un argumento de línea de comandos.

Entonces, si hacemos:

acts = ['clear','copy','dump','lock'] 
p = argparse.ArgumentParser() 
p.add_argument('action', nargs='*', choices=acts, default='clear')  
print p.parse_args([]) 

Obtenemos lo que esperamos

Namespace(action='clear') 

El problema es cuando se pone una lista como un defecto. Pero lo he visto en el doc,

parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!') 

Por lo tanto, no sé :-(

De todos modos, aquí hay una solución que hace el trabajo que desea:

import sys, argparse 
acts = ['clear','copy','dump','lock'] 
p = argparse.ArgumentParser() 
p.add_argument('action', nargs='*', choices=acts) 
args = ['dump', 'clear'] # I set the default here ... 
if sys.argv[1:]: 
    args = p.parse_args() 
print args 
1

que terminé haciendo lo siguiente:

  • sin append
  • añadir la lista vacía a las posibles opciones o bien la entrada vacío rompe
  • sin defecto
  • cheque por una lista vacía después y establecer el valor predeterminado real en ese caso

Ejemplo:

parser = argparse.ArgumentParser() 
parser.add_argument(
    'is', 
    type=int, 
    choices=[[], 1, 2, 3], 
    nargs='*', 
) 

args = parser.parse_args(['1', '3']) 
assert args.a == [1, 3] 

args = parser.parse_args([]) 
assert args.a == [] 
if args.a == []: 
    args.a = [1, 2] 

args = parser.parse_args(['1', '4']) 
# Error: '4' is not valid. 
Cuestiones relacionadas