Tutorial su Argparse in Italiano
Autore: | Tshepang Lekhonkhobe |
---|---|
Traduzione: | Lewis |
Data della traduzione: | 2 Settembre 2020 |
Questo tutorial deve essere inteso come una lieve introduzione al modulo argparse, il modulo python della standard library consigliato per l'analisi (parsing) degli argomento da linea di comando.
Note
Ci sono due altri moduli che per fare la stessa cosa, uno è getopt (l'equivalente in linguaggio C di getopt ) e l'altro è optparse , che però è deprecato. Nota anche che argparse è basato su optparse e quindi l'utilizzo è molto simile.
Concetti
Andiamo a vedere le caratteristiche funzionali che esploreremo in questo tutorial introduttivo usando il comando ls :
$ ls cpython devguide prog.py pypy rm-unused-function.patch $ ls pypy ctypes_configure demo dotviewer include lib_pypy lib-python ... $ ls -l total 20 drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython drwxr-xr-x 4 wena wena 4096 Feb 8 12:04 devguide -rwxr-xr-x 1 wena wena 535 Feb 19 00:05 prog.py drwxr-xr-x 14 wena wena 4096 Feb 7 00:59 pypy -rw-r--r-- 1 wena wena 741 Feb 18 01:01 rm-unused-function.patch $ ls --help Usage: ls [OPTION]... [FILE]... List information about the FILEs (the current directory by default). Sort entries alphabetically if none of -cftuvSUX nor --sort is specified. ...
Quello che possiamo imparare da questi quettro semplici comandi sono i seguenti concetti:
- Il comando ls funziona anche quando viene lanciato senza nessuna opzione. Di default mostra il contenuto della directory in cui ci troviamo.
- Se vogliamo andare aldilà di ciò che esso prevede di default dovremo aggiungere qualcosa. In questo caso, vorremmo mostrare il contenuto della directory pypy. Quello che in questo caso abbiamo fatto è stato specificare ciò che è noto come argomento posizionale. È chiamato così perché il programma sa cosa fare del valore in base a dove esso compare sulla linea di comando (ossia se è il primo, il secondo o il terzo argomento). Questo concetto è ancora più rilevante in comandi come cp dove l'uso di base è cp SORGENTE DESTINAZIONE. Infatti la prima posizione è ciò che vuoi venga copiato e la seconda posizione è dove vuoi venga copiato.
- Ora diciamo che vogliamo cambiare il comportamento del programma. Nel nostro terzo comando vedremo più informazioni su ogni file piuttosto che solo vedere i nomi dei file. Il -l in questo caso è conosciuto come argomento opzionale.
- Il quarto comando si riferisce allo al testo di aiuto. Questo è molto utile quando incroci un programma che non avevi mai usato prima e puoi farti un'idea di come funziona semplicemente leggendo il testo di aiuto (l'help).
Le basi
Partiamo con un esempio davvero molto semplice e che non fa quasi nulla:
import argparse parser = argparse.ArgumentParser() parser.parse_args()
Quello che segue è il risultato del codice eseguito:
$ python3 prog.py $ python3 prog.py --help usage: prog.py [-h] argomenti opzionali: -h, --help mostra questo messaggio ed esci $ python3 prog.py --verbose usage: prog.py [-h] prog.py: error: argomenti non riconosciuti: --verbose $ python3 prog.py foo usage: prog.py [-h] prog.py: error: argomenti non riconosciuti: foo
Ecco qua quello che succede:
- Far girare il programma senza nessun argomento opzionale non mostrerà risultati nello standard output. Quindi poco utile.
- Il secondo punto inizia mostrando la piena utilità del modulo argparse . Non abbiamo fatto quasi nulla ma già abbiamo un bel messaggio di aiuto.
- l'opzione --help, che puo' essere anche accorciato con -h, è l'unica opzione che ci viene data gratis (cioè non c'è bisogno di specificarla nella scrittura del codice). Specificare qualsiasi altra cosa ci restituirebbe un errore. Ma anche in questo otterremmo un messaggio utile e gratis (nel senso di automatico).
Introduzione agli argomenti posizionali
Un esempio:
import argparse parser = argparse.ArgumentParser() parser.add_argument("echo") args = parser.parse_args() print(args.echo)
e qui il codice che gira:
$ python3 prog.py usage: prog.py [-h] echo prog.py: error: the following arguments are required: echo $ python3 prog.py --help usage: prog.py [-h] echo positional arguments: echo optional arguments: -h, --help show this help message and exit $ python3 prog.py foo foo
Questo è quello che succede:
- Abbiamo aggiunto il metodo add_argument che è ciò che useremo per specificare le opzioni a linea di comando che il programma è disposto ad accettare. In questo caso, l'ho chiamato echo perché è in linea con la sua funzione.
- Ora facendo girare il nostro programma ci richiede di specificare un'opzione.
- Il metodo parse_args effettivamente restituisce alcuni dati dalle opzioni specificate, in questo caso echo.
- La variabile (echo) viene creata da argparse magicamente e gratis (automaticamente, cioè non c'è bisogno di specificare in quale variabile il valore viene memorizzato). Quindi noterai che il suo nome corrisponde al nome della stringa dell'argomento passato al metodo, in questo caso echo.
Nota inoltre che, sebbene l'aiuto sia bello e tutto il resto, attualmente non è sufficientemente utile come dovrebbe essere. Per esempio vediamo che abbiamo echo come argomento posizionale, ma non possiamo sapere cosa faccia se non indovinando o leggendo il sorgente. Quindi facciamolo un po' più utile:
import argparse parser = argparse.ArgumentParser() parser.add_argument("echo", help="echo la stringa che vedi qui") args = parser.parse_args() print(args.echo)
E questo è quello che otteniamo:
$ python3 prog.py -h usage: prog.py [-h] echo positional arguments: echo echo la stringa che vedi qui optional arguments: -h, --help mostra questo help ed esci
Ed ora come fare qualcosa di ancora più utile:
import argparse parser = argparse.ArgumentParser() parser.add_argument("quadrato", help="calcola il quadrato di un dato numero") args = parser.parse_args() print(args.quadrato**2)
Quello che segue è il risultato del codice eseguito:
$ python3 prog.py 4 Traceback (most recent call last): File "prog.py", line 5, in <module> print(args.quadrato**2) TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
Non è andata così bene. Questo perchè argparse tratta le opzioni che gli abbiamo passato come stringhe, a menochè non gli diciamo diversamente. Quindi diciamo ad argparse di trattare l'input come se fosse un intero:
import argparse parser = argparse.ArgumentParser() parser.add_argument("quadrato", help="calcola il quadrato di un dato numero", type=int) args = parser.parse_args() print(args.quadrato**2)
Qui di seguito il risultato del codice eseguito:
$ python3 prog.py 4 16 $ python3 prog.py four usage: prog.py [-h] quadrato prog.py: error: argument quadrato: invalid int value: 'four'
Questa è andata bene. Il programma ora, oltre a fornire l'help, esce anche correttamente se gli viene fornito un input non corretto.
Introduzione agli argomenti opzionali
Finora abbiamo lavorato con gli argomenti posizionali. Adesso diamo un'occhiata a come aggiungere quelli opzionali:
import argparse parser = argparse.ArgumentParser() parser.add_argument("--verbosity", help="aumenta la verbosità dell'output") args = parser.parse_args() if args.verbosity: print("verbosity turned on")
E l'output:
$ python3 prog.py --verbosity 1 verbosity turned on $ python3 prog.py $ python3 prog.py --help usage: prog.py [-h] [--verbosity VERBOSITY] optional arguments: -h, --help mostra il messaggio di help ed esci --verbosity VERBOSITY aumenta la verbosità dell'output $ python3 prog.py --verbosity usage: prog.py [-h] [--verbosity VERBOSITY] prog.py: error: argument --verbosity: expected one argument
Ecco quello che sta succedendo:
- Il programma è scritto affinchè mostri qualcosa quando --verbosity è specificato e non mostri nulla quando non lo è.
- Mostra che l'opzione è effettivamente opzionale, non ci sono errori quando il programma gira senza di essa. Nota che di default, se un argomento opzionale non è usato, alla variabile rilevante, in questo caso args.verbosity , come valore è stato dato None, che è la ragione per cui fallisce il vero test dell'espressione if .
- Il messaggio di help un lievemente differente.
- Quando si usa l'opzione --verbose, uno deve anche specificare dei valori, nessun valore.
L'esempio summenzionato accetta valori interi arbitrari per --verbosity, ma per il nostro semplice programma , solo due valori sono veramente utili, True o False. Andiamo a modificare il codice di conseguenza:
import argparse parser = argparse.ArgumentParser() parser.add_argument("--verbose", help="aumenta la verbosità dell'output", action="store_true") args = parser.parse_args() if args.verbose: print("verbosity turned on")
E l'output:
$ python3 prog.py --verbose verbosity turned on $ python3 prog.py --verbose 1 usage: prog.py [-h] [--verbose] prog.py: error: unrecognized arguments: 1 $ python3 prog.py --help usage: prog.py [-h] [--verbose] optional arguments: -h, --help show this help message and exit --verbose increase output verbosity
Ecco quello che succede:
- L'opzione ora è più di un flag quindi qualcosa che richiede un valore. Abbiamo persino cambiato il nome dell'opzione in modo che riflettesse questa idea. Nota che ora specifichiamo una nuova keywaord, action, e gli diamo il valore store_true. Questo significa che, se specificato, assegnamo a args.verbose il valore di True. Mentre se non specifichiamo nulla significa False.
- al secondo comando si arrabbia se specifichi un valore, nel vero spirito di cosa realmente sia un flag.
- al terzo comando ti restituisce un messaggio di aiuto differente.
Opzioni brevi
Se hai familiarità con la linea di comando, ti sarai accorto che non ho ancora affrontato della versione breve delle opzioni. Ma è abbastanza semplice:
import argparse parser = argparse.ArgumentParser() parser.add_argument("-v", "--verbose", help="Aumenta la verbosità dell'output", action="store_true") args = parser.parse_args() if args.verbose: print("verbosity turned on")
Ecco quello che abbiamo:
$ python3 prog.py -v verbosity turned on $ python3 prog.py --help usage: prog.py [-h] [-v] optional arguments: -h, --help mostra il messaggio di help ed esci -v, --verbose aumenta la verbosità dell'output
Nota che la nuova abilità (l'opzione breve ndt) si riflette anche nel messaggio di help.
Combinare argomenti posizionali e opzionali
Il nostro programma continua a crescere in complessità:
import argparse parser = argparse.ArgumentParser() parser.add_argument("quadrato", type=int, help="calcola il quadrato di un dato numero") parser.add_argument("-v", "--verbose", action="store_true", help="aumenta la verbosità dell'output") args = parser.parse_args() risposta = args.quadrato**2 if args.verbose: print(f"Il quadrato di {args.quadrato} uguale {risposta}") else: print(risposta)
Ed ecco ora l'output:
$ python3 prog.py usage: prog.py [-h] [-v] quadrato prog.py: errore: i seguenti argomenti sono richiesti: quadrato $ python3 prog.py 4 16 $ python3 prog.py 4 --verbose the square of 4 equals 16 $ python3 prog.py --verbose 4 the square of 4 equals 16
- Abbiamo rimesso l'argomento posizionale per questo si è arrabbiato.
- Nota che l'ordine non importa.
Che ne dite di dare al nostro programma l'abilità di potere assegnare alla verbosità livelli multipli e poi usarli davvero:
import argparse parser = argparse.ArgumentParser() parser.add_argument("quadrato", type=int, help="calcola il quadrato di un dato numero") parser.add_argument("-v", "--verbosity", type=int, help="aumenta la verbosità dell'output") args = parser.parse_args() risposta = args.quadrato**2 if args.verbosity == 2: print(f"il quadrato di {args.quadrato} uguale {risposta}") elif args.verbosity == 1: print(f"{args.quadrato}^2 == {risposta}") else: print(risposta)
E l'output:
$ python3 prog.py 4 16 $ python3 prog.py 4 -v usage: prog.py [-h] [-v VERBOSITY] quadrato prog.py: error: argomento -v/--verbosity: un argomento richiesto $ python3 prog.py 4 -v 1 4^2 == 16 $ python3 prog.py 4 -v 2 il quadrato di 4 uguale 16 $ python3 prog.py 4 -v 3 16
Questi vanno tutti bene eccetto l'ultimo, il quale espone un bug nel nostro programma. Andiamo a risolverlo restringendo i valori che puo' accettare l'opzione --verbosity:
import argparse parser = argparse.ArgumentParser() parser.add_argument("quadrato", type=int, help="calcola il quadrato di un dato numero") parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2], help="autmenta la verbosità dell'output") args = parser.parse_args() risposta = args.quadrato**2 if args.verbosity == 2: print(f"il quadrato di {args.quadrato} è uguale a {risposta}") elif args.verbosity == 1: print(f"{args.quadrato}^2 == {risposta}") else: print(risposta)
E l'output:
$ python3 prog.py 4 -v 3 usage: prog.py [-h] [-v {0,1,2}] quadrato prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2) $ python3 prog.py 4 -h usage: prog.py [-h] [-v {0,1,2}] quadrato argomenti posizionali: quadrato calcola il quadrato di un dato numero argomenti opzionali: -h, --help mostra questo help ed esci -v {0,1,2}, --verbosity {0,1,2} aumenta la verbosità dell'output
Nota che il cambiamento si riflette sia nel messaggio di errore che nella stringa di help. Ora usiamo un approccio differente, ma che è abbastanza usuale, per divertirci con la verbosità. Questo modo di gestire l'argomento della verbosità è lo stesso utilizzato dall'eseguibile CPython (prova tu stesso consultando l'output di python --help):
import argparse parser = argparse.ArgumentParser() parser.add_argument("quadrato", type=int, help="calcola il quadrato di un dato numero") parser.add_argument("-v", "--verbosity", action="count", help="aumenta la verbosità dell'output") args = parser.parse_args() risposta = args.quadrato**2 if args.verbosity == 2: print(f"il quadrato di {args.quadrato} è uguale a {risposta}") elif args.verbosity == 1: print(f"{args.quadrato}^2 == {risposta}") else: print(risposta)
qui abbiamo introdotto un'altra azione, "count", per contare il numero di volte che uno specifico argomento opzionale viene usato:
$ python3 prog.py 4 16 $ python3 prog.py 4 -v 4^2 == 16 $ python3 prog.py 4 -vv il quadrato di 4 è uguale a 16 $ python3 prog.py 4 --verbosity --verbosity the quadrato of 4 equals 16 $ python3 prog.py 4 -v 1 usage: prog.py [-h] [-v] quadrato prog.py: error: unrecognized arguments: 1 $ python3 prog.py 4 -h usage: prog.py [-h] [-v] quadrato argomenti posizionali: quadrato display a quadrato of a given number argomenti opzionali: -h, --help mostra questo messaggio di help ed esci -v, --verbosity aumenta il livello di verbosità $ python3 prog.py 4 -vvv 16
- Si, ora c'è più di un flag (in modo simile a action="store_true") rispetto alla versione precedente del nostro scriptino. Questo dovrebbe spiegare perchè da errore.
- Si comporta anche in modo simile all'azione "store_true".
- Ora qui abbiamo una dimostrazione di ciò che l'azione "count" dà. Probabilmente hai già visto prima questa specie di uso.
- E se non specifichi il flag -v, allora il flag viene considerato come se avesse valore None.
- Come ci si dovrebbe aspettare specificando la forma lunga del flag dovremmo ottenere lo stesso output.
- Purtroppo l'output del nostro help non è molto informativo sulla nuova abilità che il nostro script ha acquisito, ma questo problema puo' sempre essere risolto migliorando la qualità della nostra documentazione (per esempio mediante la l'argomento help).
- quest'ultimo output mostra un bug nel nostro programma.
Risolviamolo:
import argparse parser = argparse.ArgumentParser() parser.add_argument("quadrato", type=int, help="display a quadrato of a given number") parser.add_argument("-v", "--verbosity", action="count", help="increase output verbosity") args = parser.parse_args() risposta = args.quadrato**2 # bugfix: replace == with >= if args.verbosity >= 2: print(f"Il quadrato di {args.quadrato} è uguale a {risposta}") elif args.verbosity >= 1: print(f"{args.quadrato}^2 == {risposta}") else: print(risposta)
E questo è ciò che ci dà:
$ python3 prog.py 4 -vvv Il quadrato di 4 è uguale a 16 $ python3 prog.py 4 -vvvv il quadrato di 4 è uguale a 16 $ python3 prog.py 4 Traceback (most recent call last): File "prog.py", line 11, in <module> if args.verbosity >= 2: TypeError: '>=' not supported between instances of 'NoneType' and 'int'
- il primo output è andato bene ed è stato risolto il bug che avevamo prima. Questo è come segue: vogliamo che qualsiasi valore >=2 sia il più verboso possibile.
- Il terzo output non è un granchè.
Risolviamo questo bug:
import argparse parser = argparse.ArgumentParser() parser.add_argument("quadrato", type=int, help="display a quadrato of a given number") parser.add_argument("-v", "--verbosity", action="count", default=0, help="aumenta il livello di verbosità dell'output") args = parser.parse_args() risposta = args.quadrato**2 if args.verbosity >= 2: print(f"il quadrato di {args.quadrato} è uguale a {risposta}") elif args.verbosity >= 1: print(f"{args.quadrato}^2 == {risposta}") else: print(risposta)
Abbiamo appena introdotto un'altra keyword, default. E l'abbiamo settata a 0 in modo da renderla comparabile ad altri valori interi. Ricordalo di default, se un argomento opzionale non è stato specificato. Esso restituisce il valore None e questo non puo' essere comparato ad un valore intero (perciò abbiamo l'eccezzione TypeError ).
E:
$ python3 prog.py 4 16
Con quello che abbiamo imparato finora possiamo starcene tranquilli, e abbiamo appena guardato in superficie. Il modulo argparse è molto potente, e lo esploreremo ancora un po' prima di concludere questo tutorial.
Addentriamoci in qualcosa di un po' più avanzato
Che fare se vogliamo espandere il nostro piccolo programma in modo che faccia altre potenze, non solo i quadrati:
import argparse parser = argparse.ArgumentParser() parser.add_argument("x", type=int, help="la base") parser.add_argument("y", type=int, help="l'esponente") parser.add_argument("-v", "--verbosity", action="count", default=0) args = parser.parse_args() risposta = args.x**args.y if args.verbosity >= 2: print(f"{args.x} la potenza di {args.y} è uguale a {risposta}") elif args.verbosity >= 1: print(f"{args.x}^{args.y} == {risposta}") else: print(risposta)
Output:
$ python3 prog.py usage: prog.py [-h] [-v] x y prog.py: error: the following arguments are required: x, y $ python3 prog.py -h usage: prog.py [-h] [-v] x y positional arguments: x la base y l'esponente argomenti opzionali: -h, --help mostra questo help ed esci -v, --verbosity $ python3 prog.py 4 2 -v 4^2 == 16
Considera che finora abbiamo usato il livello di verbosità a cambia il testo che è stato mostrato. Il seguente esempio infatti usa un livello di verbosità da mostrare invece more testo:
import argparse parser = argparse.ArgumentParser() parser.add_argument("x", type=int, help="la base") parser.add_argument("y", type=int, help="l'esponente") parser.add_argument("-v", "--verbosity", action="count", default=0) args = parser.parse_args() risposta = args.x**args.y if args.verbosity >= 2: print(f"lanciando '{__file__}'") if args.verbosity >= 1: print(f"{args.x}^{args.y} == ", end="") print(risposta)
Output:
$ python3 prog.py 4 2 16 $ python3 prog.py 4 2 -v 4^2 == 16 $ python3 prog.py 4 2 -vv Running 'prog.py' 4^2 == 16
Conflitti di opzioni
Finora abbiamo lavorato con i due metodi dell'istanza di ArgumentParser . Adesso ne introdurremo un terzo, add_mutually_exclusive_group . Esso ci permette di specificare opzioni che sono in conflitto fra loro. Quindi cambiamo il resto del programma per dare senso alla nuova funzionalità: introduciamo l'opzione -quiet, la quale sarà in opposizione a --verbose:
import argparse parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group() group.add_argument("-v", "--verbose", action="store_true") group.add_argument("-q", "--quiet", action="store_true") parser.add_argument("x", type=int, help="la base") parser.add_argument("y", type=int, help="l'esponente") args = parser.parse_args() answer = args.x**args.y if args.quiet: print(risposta) elif args.verbose: print(f"{args.x} la potenza di {args.y} è uguale a {risposta}") else: print(f"{args.x}^{args.y} == {risposta}")
A questo punto il nostro programma è più semplice e abbiamo perso alcune funzionalità per il sake della dimostrazione. Comunque ecco l'output:
$ python3 prog.py 4 2 4^2 == 16 $ python3 prog.py 4 2 -q 16 $ python3 prog.py 4 2 -v 4 alla potenza di 2 è uguale a 16 $ python3 prog.py 4 2 -vq usage: prog.py [-h] [-v | -q] x y prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose $ python3 prog.py 4 2 -v --quiet usage: prog.py [-h] [-v | -q] x y prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
Questo dovrebbe essere semplice da comprendere. Abbiamo aggiunto l'ultimo output così puoi vedere la flessibilità che abbiamo ottenuto, in altre parole mischiare le opzioni in forma lunga con quelle in forma breve.
Prima di concludere in caso i tuoi utenti non sappiano la funziona del tuo programma gliela potrai dire in questo modo:
import argparse parser = argparse.ArgumentParser(description="calculate X to the power of Y") group = parser.add_mutually_exclusive_group() group.add_argument("-v", "--verbose", action="store_true") group.add_argument("-q", "--quiet", action="store_true") parser.add_argument("x", type=int, help="la base") parser.add_argument("y", type=int, help="l'esponente") args = parser.parse_args() risposta = args.x**args.y if args.quiet: print(risposta) elif args.verbose: print("{} la potenza di {} è uguale a {}".format(args.x, args.y, risposta)) else: print("{}^{} == {}".format(args.x, args.y, risposta))
Nota che c'è una minima differenza nel testo che mostra l'uso dello script. In particoalre [-v | -q], ci dicono che possiamo o usare -v oppure -q, ma non entrambi contemporaneamente:
$ python3 prog.py --help usage: prog.py [-h] [-v | -q] x y calcola X alla potenza di Y positional arguments: x la base y l'esponente optional arguments: -h, --help mostro il messaggio di help ed esci -v, --verbose -q, --quiet
Conclusioni
Il modulo argparse offre molto di più di quello che abbiamo visto fino a qui e la sua documentazione è piuttosto dettagliata e completa oltre che piena di esempi. Passando in rassegna questo tutorial dovresti essere in grado di digerire la documentazione senza sentirti sopraffatto.