In Python, usando argparse, consenti solo numeri interi positivi


164

Il titolo riassume praticamente ciò che mi piacerebbe che fosse successo.

Ecco quello che ho, e mentre il programma non esplode su un numero intero non positivo, voglio che l'utente sia informato che un numero intero non positivo è fondamentalmente senza senso.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-g", "--games", type=int, default=162,
                    help="The number of games to simulate")
args = parser.parse_args()

E l'output:

python simulate_many.py -g 20
Setting up...
Playing games...
....................

Uscita con un negativo:

python simulate_many.py -g -2
Setting up...
Playing games...

Ora, ovviamente, potrei semplicemente aggiungere un se per determinare se if args.gamesè negativo, ma ero curioso di sapere se c'era un modo per intrappolarlo a argparselivello, in modo da sfruttare la stampa automatica dell'utilizzo.

Idealmente, stamperebbe qualcosa di simile a questo:

python simulate_many.py -g a
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid int value: 'a'

Così:

python simulate_many.py -g -2
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid positive int value: '-2'

Per ora lo sto facendo, e immagino di essere felice:

if args.games <= 0:
    parser.print_help()
    print "-g/--games: must be positive."
    sys.exit(1)

Risposte:


244

Questo dovrebbe essere possibile utilizzando type. Dovrai comunque definire un metodo effettivo che decida questo per te:

def check_positive(value):
    ivalue = int(value)
    if ivalue <= 0:
        raise argparse.ArgumentTypeError("%s is an invalid positive int value" % value)
    return ivalue

parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=check_positive)

Questo è fondamentalmente solo un esempio adattato dalla perfect_squarefunzione nei documenti su argparse.


1
La tua funzione può avere più valori? Come funziona?
Tom

2
Se la conversione intfallisce, ci sarà comunque un output leggibile? O dovresti try raiseconvertirlo manualmente?
NO

4
@MrZ Darà qualcosa di simile error: argument foo: invalid check_positive value: 'foo=<whatever>'. Potresti semplicemente aggiungere un try:... except ValueError:attorno ad esso che genera un'eccezione con un messaggio di errore migliore.
Yuushi,

59

type sarebbe l'opzione consigliata per gestire le condizioni / i controlli, come nella risposta di Yuushi.

Nel tuo caso specifico, puoi anche usare il choicesparametro se il tuo limite superiore è anche noto:

parser.add_argument('foo', type=int, choices=xrange(5, 10))

Nota: utilizzare rangeinvece di xrangeper python 3.x


3
Immagino che questo sarebbe abbastanza inefficiente, dato che genereresti un intervallo e poi scorrerai attraverso di esso per confermare i tuoi input. Una rapida ifè molto più veloce.
TravisThomas

2
@ trav1th In effetti potrebbe essere, ma è un esempio di utilizzo dai documenti. Inoltre, ho detto nella mia risposta che la risposta di Yuushi è quella da prendere. Buono a dare opzioni. E nel caso di argparse, accade una volta per esecuzione, usa un generatore ( xrange) e non richiede codice aggiuntivo. Tale compromesso è disponibile. Spetta a ciascuno di decidere quale strada da percorrere.
aneroide,

16
Per essere più chiari sul punto di jgritty sulla risposta dell'autore ben, scelte = xrange (0,1000) comporterà la scrittura sull'intero elenco di numeri interi compresi tra 1 e 999 ogni volta che si utilizza --help o se è presente un argomento non valido fornito. Non è una buona scelta nella maggior parte dei casi.
biomiker,

9

Il modo rapido e sporco, se hai un massimo prevedibile e un minimo per il tuo arg, è usare choicescon un intervallo

parser.add_argument('foo', type=int, choices=xrange(0, 1000))

24
Il rovescio della medaglia è l'output orribile.
jgritty

6
enfasi sullo sporco , immagino.
ben autore

4
Per essere più chiari sul punto di jgritty, options = xrange (0,1000) comporterà la scrittura sull'intero elenco di numeri interi compresi tra 1 e 999 ogni volta che si utilizza --help o se viene fornito un argomento non valido. Non è una buona scelta nella maggior parte dei casi.
biomiker,

8

Un'alternativa più semplice, specialmente se la sottoclasse argparse.ArgumentParser, è iniziare la validazione dall'interno del parse_argsmetodo.

All'interno di tale sottoclasse:

def parse_args(self, args=None, namespace=None):
    """Parse and validate args."""
    namespace = super().parse_args(args, namespace)
    if namespace.games <= 0:
         raise self.error('The number of games must be a positive integer.')
    return namespace

Questa tecnica potrebbe non essere interessante come un callable personalizzato, ma fa il lavoro.


Circa ArgumentParser.error(message):

Questo metodo stampa un messaggio di utilizzo che include il messaggio sull'errore standard e termina il programma con un codice di stato 2.


Credito: risposta di Jonatan


O almeno, sostituendolo print "-g/--games: must be positive."; sys.exit(1)con just parser.error("-g/--games: must be positive."). (Uso come nella risposta di Jonatan .)
Aneroide

3

Nel caso in cui qualcuno (come me) incontri questa domanda in una ricerca su Google, ecco un esempio di come utilizzare un approccio modulare per risolvere ordinatamente il problema più generale di consentire numeri interi di argparse solo in un intervallo specificato :

# Custom argparse type representing a bounded int
class IntRange:

    def __init__(self, imin=None, imax=None):
        self.imin = imin
        self.imax = imax

    def __call__(self, arg):
        try:
            value = int(arg)
        except ValueError:
            raise self.exception()
        if (self.imin is not None and value < self.imin) or (self.imax is not None and value > self.imax):
            raise self.exception()
        return value

    def exception(self):
        if self.imin is not None and self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an integer in the range [{self.imin}, {self.imax}]")
        elif self.imin is not None:
            return argparse.ArgumentTypeError(f"Must be an integer >= {self.imin}")
        elif self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an integer <= {self.imax}")
        else:
            return argparse.ArgumentTypeError("Must be an integer")

Questo ti permette di fare qualcosa come:

parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=IntRange(1))     # Must have foo >= 1
parser.add_argument('bar', type=IntRange(1, 7))  # Must have 1 <= bar <= 7

La variabile fooora consente solo numeri interi positivi , come richiesto dall'OP.

Si noti che oltre ai moduli precedenti, è possibile anche solo un massimo con IntRange:

parser.add_argument('other', type=IntRange(imax=10))  # Must have other <= 10
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.