Il Modulo ArgParse - Pt. 2

Nella prima parte di questa lezione abbiamo creato una calcolatrice da utilizzare tramite riga di comando: funziona piuttosto bene, anche se è abbastanza minimalista.

I parametri utilizzati finora dallo script, come avete letto nella descrizione dell'help, sono parametri posizionali o di posizione, necessari quindi al corretto funzionamento dello script: per ottenere una versione un po' più verbosa e ricca dobbiamo aggiungere dei parametri opzionali ai quali, qualora non vengano specificati, viene assegnato il valore None di default.


Come aggiungere parametri opzionali

I parametri opzionali possono avere due versioni, e sono contraddistinti dall'utilizzo del trattino - per la versione abbreviata [come per -h] e dal doppio trattino -- nel caso della versione estesa [come per --help]

Procediamo ad aggiungere un parametro opzionale:

parser.add_argument("-v", "--verbose", help="Restituisce output verboso.", action="store_true")

Di default, l'azione associata è semplicemente "store", che significa archiviare, mettere in memoria: action="store_true" significa che vogliamo depositare il valore True nella variabile verbose quando questa viene passata come parametro. Salvando un valore booleano in questa maniera fa si che ci si riferisca spesso a questi parametri come flags. A questo punto possiamo implementare l'opzione in questa maniera:

risultato = calcolatrice(args.n1, args.n2, args.operazione)
if args.verbose:
    print(f"Il risultato dell'operazione '{ args.operazione }' è: { risultato }")
else:
    print(risultato)

Analizziamo il messaggio di aiuto:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py --help

# output
usage: calc_argparse.py [-h] [-v] n1 n2 {add,sot,mol,div}

Semplice calcolatrice per addizioni, sottrazioni, moltiplicazioni e divisioni

positional arguments:
    n1                   Primo Numero
    n2                   Secondo Numero
    {add,sot,mol,div}    Tipo di Operazione

optional arguments:
    -h, --help         show this help message and exit
    -v, --verbose      Restituisce output verboso.

Ecco che tra gli argomenti opzionali abbiamo anche verbose. Testiamolo subito:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 333 111 sot -v

# output
Il risultato dell'operazione è: 222.0

E a conferma della loro natura opzionale, proviamo la stessa operazione ma senza questo parametro:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 333 111 sot

# output
222.0

Abbiamo accennato che, per i parametri opzionali, l'azione di default è store, che significa immagazzinare, archiviare, mettere in memoria. Ciò significa che in effetti, oltre ad utilizzare i parametri opzionali come flags, possiamo utilizzarli anche per depositarci dei valori aggiuntivi. Ad esempio, facciamo una piccola modifica al codice:

(...)

parser.add_argument("-v", "--verbose", help="Restituisce output verboso.")

(...)

if args.verbose:
    print(f"Il risultato dell'operazione '{args.operazione}' è: {risultato}")
    print("Verbose:", args.verbose)

Lanciamo lo script:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 11 3 mol --verbose 555

# output
Il risultato dell'operazione è: 33.0
Verbose: 555

Stessa cosa sarebbe successa chiaramente se avessi passato una stringa di caratteri testuali:

pymike@harley:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 11 3 mol --verbose /spam/eggs/bacon

# output
Il risultato dell'operazione è: 33.0
Verbose: /spam/eggs/bacon

Com'è facile immaginare, questo può tornarci utile in svariati casi.

Per restare in tema con la nostra calcolatrice, possiamo passare a verbose un intero e tramite questo selezionare diversi livelli di verbosità dell'output:

(...)
parser.add_argument("-v", "--verbose", help="Restituisce output verboso.", type=int, choices=[1,2])
args = parser.parse_args()

risultato = calcolatrice(args.n1, args.n2, args.operazione)

if args.verbose == 1:
    print(f"Il risultato dell'operazione '{args.operazione}' è: {risultato}")
elif args.verbose == 2:
    print("Benvenuti al programma Calcolatrice!")
    print(f"Il risultato dell'operazione '{args.operazione}' tra '{args.n1}' e '{args.n2}' è: {risultato}")
else:
    print(f"Il risultato dell'operazione è: {risultato}")

Testiamo il messaggio di aiuto:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py --help

# output
usage: calc_argparse.py [-h] [-v {1,2}] n1 n2 {add,sot,mol,div}

Semplice calcolatrice per addizioni, sottrazioni, moltiplicazioni e divisioni

positional arguments:
    n1                    Primo Numero
    n2                    Secondo Numero
    {add,sot,mol,div}     Tipo di Operazione

optional arguments:
    -h, --help                 show this help message and exit
    -v {1,2}, --verbose {1,2}  Restituisce output verboso.

E ora il programma:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 90 9 div -v 2

# output
Benvenuti al programma Calcolatrice!
Il risultato dell'operazione 'div' tra '90.0' e '9.0' è: 10.0

In questo caso specifico possiamo utilizzare anche count, ovvero un'altra action associabile molto utile e impiegata spesso proprio per aumentare i livelli di verbosità.

parser.add_argument("-v", "--verbose", help="Restituisce output verboso", action="count")

Ora verrà depositato un numero pari a quelle che sono le volte che richiamiamo il parametro opzionale, ad esempio se facessi "-vvv" verrebbe associato il valore 3. Quindi modifico anche le clausole di controllo cambiando "== 2" con ">= 2":

elif args.verbose >= 2:
    print("Benvenuti al programma calcolatrice!")
    print(f"Il risultato dell'operazione '{args.operazione}' tra '{args.n1}' e '{args.n2}' è: {risultato}")

Ora posso testare l'opzione:

pymike@harley:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 33 11 add -v

# output
Il risultato dell'operazione 'add' è: 44.0

pymike@harley:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 33 11 add -vv

# output
Benvenuti al programma calcolatrice!
Il risultato dell'operazione 'add' tra '33.0' e '11.0' è: 44.0

Il programma funziona bene, ma se ci dimentichiamo di passare almeno un "-v" otteniamo errore! Questo perché con action="count", se non passiamo almeno una volta il parametro, a questo viene associato il valore None.

pymike@harley:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 33 11 add

# output
Traceback (most recent call last): File "./calc_argparse.py", line 28, in elif args.verbose >= 2: TypeError: '>=' not supported between instances of 'NoneType' and 'int'

Possiamo aggirare il problema passando un valore di default, in modo che se decidiamo di usare il programma in modalità non verbosa, venga associato pur sempre un intero:

parser.add_argument("-v", "--verbose", help="Restituisce output verboso", action="count", default=0)


I parametri ad esclusione reciproca

Per completezza, dopo aver mostrato parametri posizionali e opzionali, parliamo ora di argomenti ad esclusione reciproca, in inglese Mutually Exclusive Argument, ovvero parametri che fanno parte di uno stesso gruppo, in cui possiamo quindi sceglierne solamente uno.

Nell'esempio della nostra semplice calcolatrice, diciamo che vogliamo definire oltre all'opzione "--verbose" anche un'opzione "--quiet", che ci mostra semplicemente il risultato senza nessun'altra informazione.

Dobbiamo quindi anzitutto creare un gruppo, per convenzione lo chiameremo "group", e quindi aggiungere a questo gruppo i parametri opzionali associati, e quindi modificare il resto del codice in maniera appropriata:

group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", help="Restituisce output verboso.", type=int, choices=[1,2])
group.add_argument("-q", "--quiet", help="Output non verboso", action="store_true")
args = parser.parse_args()

risultato = calcolatrice(args.n1, args.n2, args.operazione)

if args.quiet:
    print(risultato)
elif args.verbose == 1:
    print(f"Il risultato dell'operazione '{args.operazione}' è: {risultato}")
elif args.verbose == 2:
    print("Benvenuti al programma Calcolatrice!")
    print(f"Il risultato dell'operazione '{args.operazione}' tra '{args.n1}' e '{args.n2}' è: {risultato}")
else:
    print(f"Il risultato dell'operazione è: {risultato}")

Bene, proviamo ora a richiamare la funzione help sul nostro script:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py -h

# output
usage: calc_argparse.py [-h] [-v {1,2} | -q] n1 n2 {add,sot,mol,div}

Semplice calcolatrice per addizioni, sottrazioni, moltiplicazioni e divisioni

positional arguments:
    n1                    Primo Numero
    n2                    Secondo Numero
    {add,sot,mol,div}     Tipo di Operazione

optional arguments:
    -h, --help                 show this help message and exit
    -v {1,2}, --verbose {1,2}  Restituisce output verboso.
    -q, --quiet                Output non verboso

Quindi, se lanciamo il programma con il flag quite attivo:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 90 9 div -q

# output
10.0

Mentre, chiaramente, trattandosi di parametri ad esclusione reciproca, se chiamiamo sia verbose che quite, otteniamo un errore:

pymike@programmareinpython:~/Desktop/lezione_argparse$ python3.6 ./calc_argparse.py 90 9 div -q -v 2

# output
usage: calc_argparse.py [-h] [-v {1,2} | -q] n1 n2 {add,sot,mol,div}
calc_argparse.py: error: argument -v/--verbose: not allowed with argument -q/--quiet

Come al solito, Happy Coding!