2011-12-15 18 views
17

Ho uno script in cui chiedo all'utente un elenco di azioni predefinite da eseguire. Voglio anche la possibilità di assumere un elenco particolare di azioni quando l'utente non definisce nulla. tuttavia, sembra che provare a fare entrambe queste cose sia impossibile.python argparse - argomento append opzionale con scelte

quando l'utente dà nessun argomento, ricevono un errore che la scelta di default è valido

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

e quando lo fanno definire una serie di azioni, lo spazio dei nomi risultante ha le azioni dell'utente aggiunti al valore predefinito, invece di sostituire il default

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

Non sono sicuro che questo problema sia stato risolto da un bug simile: http://bugs.python.org/issue9625. Un modo possibile per gestirlo è usare un'azione personalizzata, piuttosto che la parola chiave 'choices'. Vedi la risposta accettata su [questa domanda] (http://stackoverflow.com/questions/4194948/python-argparse-is-there-a--to-specify-a-range-in-nargs) – Chris

risposta

15

quello che ti serve può essere fatto utilizzando una misura argparse.Action come il seguente esempio:

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

l'output dello script è:

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

l'azione veniva aggiunto a causa della "action = 'append'" parametro passato a argparse.

Dopo aver rimosso questo parametro, gli argomenti passati da un utente verrebbero visualizzati autonomamente, ma il programma genererebbe un errore quando non sono stati passati argomenti.

L'aggiunta di un prefisso '-' al primo parametro lo risolve nel modo più pigro.

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

L'aspetto negativo di questo approccio è che le opzioni passate dall'utente devono ora essere preceduti da '--action', come:

app.py --action clear dump copy 
3

Si potrebbe verificare se l'utente è la fornitura di azioni (nel qual caso analizzare come, argomento obbligatorio posizione), o fornisca alcuna azione (nel qual caso analizzarlo come argomento opzionale con predefinito):

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) 

quando eseguito, cede questi risultati:

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

Probabilmente hanno altre opzioni per analizzare pure. In tal caso, è possibile utilizzare parse_known_args per analizzare le altre opzioni, e quindi gestire le unknown argomentazioni in un secondo passaggio:

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) 

quando eseguito, produce questi risultati:

% 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

Nella documentazione (http://docs.python.org/dev/library/argparse.html#default), si dice:

Per gli argomenti posizionali con nargs pari a? o *, il valore predefinito viene utilizzato quando non era presente l'argomento della riga di comando.

Poi, se lo facciamo:

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

Otteniamo quello che ci aspettiamo

Namespace(action='clear') 

Il problema è quando si mette un elenco come predefinito. Ma ho visto nel doc,

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

Quindi, non so :-(

In ogni caso, ecco una soluzione che fa il lavoro che si desidera:

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

ho finito per fare il seguente:

  • senza append
  • aggiungere l'elenco vuoto alle scelte possibili oppure l'inserimento vuoto rompe
  • senza difetto
  • controllo per un elenco vuoto dopo e impostare il predefinito effettivo in quel caso

Esempio:

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. 
Problemi correlati