argparse - Analizzatore di opzioni e di argomenti di riga di comando

Scopo: Analizza le opzioni e gli argomenti di riga di comando

Il modulo argparse include strumenti per costruire processori di argomenti e opzioni da riga di comando. E' stato aggiunto a Python 2.7 come rimpiazzo per optparse. L'implementazione di argparse supporta caratteristiche che non sarebbe stato facile aggiungere a optparse, e che avrebbe richiesto modifiche all'API incompatibili con le versioni precedenti; così è stato portato nella libreria un nuovo modulo. optparse è ora deprecato.

Impostare un Analizzatore

Quando si usa argparse, il primo passo consiste nel creare un oggetto analizzatore (parser), e dirgli quale argomenti attendersi. Il parser può poi essere usato per elaborare gli argomenti di riga di comando quando il programma viene eseguito. Il costruttore per la classe ArgumentParser riceve diversi argomenti per impostare le descrizioni usate nel testo di aiuto per il programma e per altri comportamenti o impostazioni globali.

import argparse

parser = argparse.ArgumentParser(
  description='Questo è un programma di esempio di PyMOTW',
)

Definire gli Argomenti

argparse è una libreria completa per l'elaborazione degli argomenti. Essi possono attivare diverse azioni, specificate dall'argomento action di add_argument(). Le azioni supportate comprendono la memorizzazione dell'argomento (singolarmente o come parte di una lista), la memorizzazione di un valore costante quando viene rilevato l'argomento (compresa la gestione speciale per i valori true/false degli switch booleani), il conteggio del numero di volte nelle quali viene rilevato un argomento e una chiamata di callback per usare istruzioni di elaborazione personalizzate.

L'azione predefinita è la memorizzazione del valore dell'argomento. In questo caso, se viene passato un tipo, il valore viene convertito in quel tipo prima di essere memorizzato mentre se si passa l'argomento dest il valore viene salvato usando quel nome quando la riga di comando viene analizzata.

Analizzare una Riga di Comando

Una volta che tutti gli argomenti sono definiti, si può analizzare la riga di comando passando una sequenza di stringhe di argomento a parse_args(). Nella modalità predefinita, gli argomenti sono recuperati da sys.argv[1:], ma può usata una qualsiasi lista di stringhe. Le opzioni sono elaborate usando la sintassi GNU/POSIX, in modo che opzioni e valori di argomenti possano essere mescolati nella sequenza.

Il valore restituito da parse_args() è un Namespace che contiene gli argomenti del comando. L'oggetto contiene i valori degli argomenti come attributi, in modo che se l'argomento dest è "miaopzione", al suo valore si potrà accedere come args.miaopzione.

Semplici Esempi

Ecco un semplice esempio con tre diverse opzioni: una booleana (-a), una stringa (-b) e un intero (-c).

# argparse_short.py

import argparse

parser = argparse.ArgumentParser(description='Breve app di esempio')

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print(parser.parse_args(['-a', '-bvalore', '-c', '3']))

Ci sono modi diversi per passare valori alle opzioni a carattere singolo: l'esempio di cui sopra usa due forme diverse, -bvalore e -c valore.

$ python3 argparse_short.py

Namespace(a=True, b='valore', c=3)

Il tipo del valore associato a 'c'.nell'output è un intero, visto che ad ArgumentParser è stato detto di convertire l'argomento prima di memorizzarlo.

I nomi di opzione "lunghi", con un nome composto da più caratteri, sono gestiti allo stesso modo.

# argparse_long.py

import argparse

parser = argparse.ArgumentParser(
    description='Esempio con nomi di opzione lunghi',
)

parser.add_argument('--nessunargomento', action="store_true",
                    default=False)
parser.add_argument('--conargomenti', action="store",
                    dest="conargomenti")
parser.add_argument('--conargomenti2', action="store",
                    dest="conargomenti2", type=int)

print(
    parser.parse_args(
        ['--nessunargomento', '--conargomenti', 'val', '--conargomenti2=3']
    )
)

Ed i risultati sono simili:

$ python3 argparse_long.py

Namespace(conargomenti='val', conargomenti2=3, nessunargomento=True)

argparse è uno strumento completo per analizzare la riga di comando, e gestisce argomenti sia opzionali che richiesti.

# argparse_arguments.py

import argparse

parser = argparse.ArgumentParser(
    description='Esempio con parametri non opzionali',
)

parser.add_argument('count', action="store", type=int)
parser.add_argument('units', action="store")

print(parser.parse_args())

In questo esempio, l'argomento count è un intero, e l'argomento units viene salvato come stringa. Se entrambi non vengono passati nella riga di comando, oppure il valore fornito non può essere convertito nel tipo corretto, viene riportato un errore.

$ python3 argparse_arguments.py 3 centimetri

Namespace(count=3, units='centimetri')

$ python3 argparse_arguments.py alcuni centimetri

usage: argparse_arguments.py [-h] count units
argparse_arguments.py: error: argument count: invalid int value: 'alcuni'

$ python3 argparse_arguments.py

usage: argparse_arguments.py [-h] count units
argparse_arguments.py: error: the following arguments are required: count, units
Azioni degli argomenti

Una qualsiasi delle sei azioni built-in possono essere attivate quando viene rilevato un argomento:

store
Conserva il valore, dopo averlo opzionalmente convertito in un tipo diverso. Questa è l'azione predefinita intrapresa se non viene specificato esplicitamente.
store_const
Conserva un valore definito come parte della specifica dell'argomento piuttosto che un valore che proviene dall'argomento in analisi. Tipicamente usato per implementare dei flag di riga di comando che non sono booleani.
store_true / store_false
Conserva il valore booleano appropriato. Queste azioni sono usate per implementare gli switch booleani
append
Conserva il valore in una lista. Se l'argomento è ripetuto i valori multipli vengono conservati.
append_const
Conserva un valore definito nella specifica dell'argomento in una lista
version
Stampa i dettagli della versione del programma quindi esce.

Questo programma di esempio dimostra ciascun tipo di azione, con la configurazione minima necessaria al suo funzionamento.

# argparse_action.py

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-s', action='store',
                    dest='simple_value',
                    help='Conserva un valore semplice')

parser.add_argument('-c', action='store_const',
                    dest='constant_value',
                    const='value-to-store',
                    help='Conserva un valore costante')

parser.add_argument('-t', action='store_true',
                    default=False,
                    dest='boolean_t',
                    help='Imposta un interruttore a True')
parser.add_argument('-f', action='store_false',
                    default=True,
                    dest='boolean_f',
                    help='Imposta un interruttore a False')

parser.add_argument('-a', action='append',
                    dest='collection',
                    default=[],
                    help='Aggiunge velori ripetuti in una lista')

parser.add_argument('-A', action='append_const',
                    dest='const_collection',
                    const='value-1-to-append',
                    default=[],
                    help='Aggiunge valori diversi ad una lista')
parser.add_argument('-B', action='append_const',
                    dest='const_collection',
                    const='value-2-to-append',
                    help='Aggiunge valori diversi ad una lista')

parser.add_argument('--versione', action='version',
                    version='%(prog)s 1.0')

results = parser.parse_args()
print('valore_semplice  = {!r}'.format(results.simple_value))
print('valore_costante  = {!r}'.format(results.constant_value))
print('booleano_true    = {!r}'.format(results.boolean_t))
print('booleano_false   = {!r}'.format(results.boolean_f))
print('collezione       = {!r}'.format(results.collection))
print('collezione_cost. = {!r}'.format(results.const_collection))
$ python3 argparse_action.py -h

usage: argparse_action.py [-h] [-s SIMPLE_VALUE] [-c] [-t] [-f]
                          [-a COLLECTION] [-A] [-B] [--versione]

optional arguments:
  -h, --help       show this help message and exit
  -s SIMPLE_VALUE  Conserva un valore semplice
  -c               Conserva un valore costante
  -t               Imposta un interruttore a True
  -f               Imposta un interruttore a False
  -a COLLECTION    Aggiunge velori ripetuti in una lista
  -A               Aggiunge valori diversi ad una lista
  -B               Aggiunge valori diversi ad una lista
  --versione       show program's version number and exit
$ python3 argparse_action.py --versione

argparse_action.py 1.0
Prefissi di Opzione

La sintassi predefinita per le opzioni è basata sulla convenzione Unix di contraddistinguere gli switch da riga di comando usando il prefisso "-". argparse supporta altri prefissi, quindi si può rendere il proprio programma conforme ai valori predefiniti della piattaforma locale (es.: usare "/" in Windows) oppure seguire una diversa convenzione.

# argparse_prefix_chars.py

import argparse

parser = argparse.ArgumentParser(
    description='Modifica i caratteri del prefisso di opzione',
    prefix_chars='-+/',
)

parser.add_argument('-a', action="store_false",
                    default=None,
                    help='Disattiva A',
                    )
parser.add_argument('+a', action="store_true",
                    default=None,
                    help='Attiva A',
                    )
parser.add_argument('//noarg', '++noarg',
                    action="store_true",
                    default=False)

print(parser.parse_args())

Impostare il parametro prefix_chars per ArgumentParser a una stringa che contiene tutti i caratteri ammessi che dovrebbero identificare una opzione. E' importante comprendere che, sebbene prefix_chars determini i caratteri concessi per gli switch, la definizione dei singoli argomenti specifica la sintassi per un certo switch. Questo fornisce un controllo esplicito sul fatto che le opzioni che usano prefissi diversi siano alias (come potrebbe essere il caso per la sintassi di una riga di comando indipendente dalla piattaforma) o alternative (es. usare "+" per indicare l'attivazione di uno switch e "-" per la disattivazione). Nell'esempio qui sopra, +a e -a sono argomenti distinti, e //noarg può anche essere passato come ++noarg ma non come --noarg.

$ python3 argparse_prefix_chars.py -h

usage: argparse_prefix_chars.py [-h] [-a] [+a] [//noarg]

Modifica i caratteri del prefisso di opzione

optional arguments:
  -h, --help        show this help message and exit
  -a                Disattiva A
  +a                Attiva A
  //noarg, ++noarg
$ python3 argparse_prefix_chars.py +a

Namespace(a=True, noarg=False)
$ python3 argparse_prefix_chars.py -a

Namespace(a=False, noarg=False)
$ python3 argparse_prefix_chars.py //noarg

Namespace(a=None, noarg=True)
$ python3 argparse_prefix_chars.py ++noarg

Namespace(a=None, noarg=True)
$ python3 argparse_prefix_chars.py --noarg

usage: argparse_prefix_chars.py [-h] [-a] [+a] [//noarg]
argparse_prefix_chars.py: error: unrecognized arguments: --noarg
Origini degli argomenti

Negli esempi fino a qui, l'elenco degli argomenti forniti al parser provenivano da un elenco passato esplicitamente, oppure erano recuperati in modo implicito da sys.argv. Passare esplicitamente un elenco è utile quando si usa argparse per elaborare istruzioni tipo riga di comando che non provengono dalla riga di comando stessa (tipo da un file di configurazione).

# argparse_with_shlex.py

import argparse
from configparser import ConfigParser
import shlex

parser = argparse.ArgumentParser(description='Breve app di esempio')

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

config = ConfigParser()
config.read('argparse_with_shlex.ini')
config_value = config.get('cli', 'options')
print('Da configurazione :', config_value)

argument_list = shlex.split(config_value)
print('Da lista argomenti:', argument_list)

print('Risulati          :', parser.parse_args(argument_list))

Questo esempio usa il modulo configparser per leggere un file di configurazione.

[cli]
options = -a -b 2

shlex facilita la divisione della stringa memorizzata nel file di configurazione.

$ python3 argparse_with_shlex.py

Da configurazione : -a -b 2
Da lista argomenti: ['-a', '-b', '2']
Risulati          : Namespace(a=True, b='2', c=None)

Un'alternativa all'elaborare il file di configurazione nel codice dell'applicazione è di dire ad argparse come riconoscere un argomento che fa riferimento a un file di input che contiene un insieme di argomenti da elaborare tramite fromfile_prefix_chars.

# argparse_fromfile_prefix_chars.py

import argparse
import shlex

parser = argparse.ArgumentParser(description='Breve app di esempio',
                                 fromfile_prefix_chars='@',
                                 )

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print(parser.parse_args(['@argparse_fromfile_prefix_chars.txt']))

In questo esempio il programma si interrompe quando trova un argomento prefissato da @, quindi legge il file il cui nome ha ricevuto come argomento per trovare ulteriori argomenti. Ad esempio se il file di input argparse_fromfile_prefix_chars.txt contiene una serie di argomenti, uno per riga:

# argparse_fromfile_prefix_chars.txt

-a
-b
2

L'output prodotto quando viene elaborato il file è:

$ python3 argparse_fromfile_prefix_chars.py

Namespace(a=True, b='2', c=None)

Stampa di Aiuto

Aiuto Generato Automaticamente

argparse aggiungerà automaticamente le opzioni per generare l'aiuto, se opportunamente configurato. L'argomento add_help di ArgumentParser controlla le opzioni legate all'aiuto.

# argparse_with_help.py

import argparse

parser = argparse.ArgumentParser(add_help=True)

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print(parser.parse_args())

Le opzioni di aiuto (-h ed --help) sono aggiunte in modalità predefinita; possono essere disabilitate impostando add_help a false.

# argparse_without_help.py

import argparse

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print(parser.parse_args())

Sebbene -h ed --help siano uno standard defacto per i nomi di opzioni per richiedere aiuto, alcune applicazioni o usi di argparse potrebbero non avere necessità di fornire un aiuto oppure potrebbero usare questi nomi di opzione per altri scopi.

$ python3 argparse_with_help.py -h

usage: argparse_with_help.py [-h] [-a] [-b B] [-c C]

optional arguments:
  -h, --help  show this help message and exit
  -a
  -b B
  -c C
$ python3 argparse_without_help.py -h

usage: argparse_without_help.py [-a] [-b B] [-c C]
argparse_without_help.py: error: unrecognized arguments: -h
Personalizzare l'Aiuto

Per applicazioni che devono gestire la stampa dell'aiuto direttamente, alcuni metodi di convenienza di ArgumentParser sono utili per la creazione di azioni personalizzate per la stampa di aiuto con informazioni addizionali.

# argparse_custom_help.py

import argparse

parser = argparse.ArgumentParser(add_help=True)

parser.add_argument('-a', action="store_true", default=False)
parser.add_argument('-b', action="store", dest="b")
parser.add_argument('-c', action="store", dest="c", type=int)

print('print_usage output:')
parser.print_usage()
print()

print('print_help output:')
parser.print_help()

print_usage() stampa il messaggio breve, e print_help() stampa l'aiuto completo.

$ python3 argparse_custom_help.py

print_usage output:
usage: argparse_custom_help.py [-h] [-a] [-b B] [-c C]

print_help output:
usage: argparse_custom_help.py [-h] [-a] [-b B] [-c C]

optional arguments:
  -h, --help  show this help message and exit
  -a
  -b B
  -c C

ArgumentParser usa una classe di formattazione per controllare l'aspetto della stampa di aiuto. Per modificare questa classe si passi formatter_class quando si istanzia ArgumentParser.

Ad esempio il formattatore RawDescriptionHelperFormatter ignora il compattamento su di una riga fornito dal formattatore predefinito.

# argparse_raw_description_help_formatter.py

import argparse

parser = argparse.ArgumentParser(
    add_help=True,
    formatter_class=argparse.RawDescriptionHelpFormatter,
    description="""
    descrizione
        su piu'
           righe""",
    epilog="""
    conclusione
      su piu'
         righe""",
)

parser.add_argument(
    '-a', action="store_true",
    help="""argomento
    di aiuto
    viene stampato su di una sola riga
    """,
)

parser.print_help()

Tutto il testo nella descrizione e conclusione del comando rimane immutato.

$ python3 argparse_raw_description_help_formatter.py

usage: argparse_raw_description_help_formatter.py [-h] [-a]

    descrizione
        su piu'
           righe

optional arguments:
  -h, --help  show this help message and exit
  -a          argomento di aiuto viene stampato su di una sola riga

    conclusione
      su piu'
         righe

RawTextHelperFormatter considera tutto il testo di aiuto come pre-formattato.

# argparse_raw_description_help_formatter.py

import argparse

parser = argparse.ArgumentParser(
    add_help=True,
    formatter_class=argparse.RawTextHelpFormatter,
    description="""
    descrizione
        su piu'
           righe""",
    epilog="""
    conclusione
      su piu'
         righe""",
)

parser.add_argument(
    '-a', action="store_true",
    help="""argomento
    di aiuto non
    viene stampato su di una sola riga
    """,
)

parser.print_help()

Il testo di aiuto per l'argomento -a non appare più su di una sola riga.

$ python3 argparse_raw_text_help_formatter.py

usage: argparse_raw_text_help_formatter.py [-h] [-a]

    descrizione
        su piu'
           righe

optional arguments:
  -h, --help  show this help message and exit
  -a          argomento
                  di aiuto non
                  viene stampato su di una sola riga
                  

    conclusione
      su piu'
         righe

I formattatori Raw potrebbero essere utili per applicazioni con esempi nella descrizione o nella conclusione, laddove modificare il formato del testo potrebbe rendere gli esempi non validi.

MetavarTypeHelpFormatter stampa il nome del tipo di ciascuna opzione, invece della variabile di destinazione, il che può essere utile per applicazioni con molte opzioni di tipo diverso.

# argparse_metavar_type_help_formatter.py

import argparse

parser = argparse.ArgumentParser(
    add_help=True,
    formatter_class=argparse.MetavarTypeHelpFormatter,
)

parser.add_argument('-i', type=int, dest='notshown1')
parser.add_argument('-f', type=float, dest='notshown2')

parser.print_help()

Invece che visualizzare il valore di dest, viene stampato il nome del tipo associato all'opzione.

$ python3 argparse_metavar_type_help_formatter.py

usage: argparse_metavar_type_help_formatter.py [-h] [-i int] [-f float]

optional arguments:
  -h, --help  show this help message and exit
  -i int
  -f float

Organizzazione del Parser

argparse comprende diverse caratteristiche per l'organizzazione dei propri parser di argomenti, per facilitarne l'implementazione o per migliorare l'usabilità dell'output di aiuto.

Condividere Regole di Elaborazione

I programmatori in genere hanno bisogno di implementare un insieme di strumenti da riga di comando che ricevono tutti un insieme di argomenti, per poi specializzarsi in un qualche modo. Ad esempio, se i tutti i programmi necessitano di una autenticazione dell'utente prima di intraprendere una azione effettiva, potrebbero avere tutti bisogno di supportare le opzioni --user e --password. Piuttosto che aggiungere esplicitamente le opzioni a ogni ArgumentParser, si può definire un parser "genitore" con le opzioni condivise, poi fare da esso ereditare ai parser dei singoli programmi le sue opzioni.

Il primo passo è impostare il parser con le definizioni degli argomenti comuni. Visto che ogni utilizzatore del parser genitore vorrà cercare di aggiungere le stesse opzioni di aiuto, causando una eccezione, occorre disabilitare la generazione di aiuto automatico nel parser di base.

# argparse_parent_base.py

import argparse

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('--user', action="store")
parser.add_argument('--password', action="store")

Quindi si crea un altro parser con impostato parents;

# argparse_uses_parent.py

import argparse
import argparse_parent_base

parser = argparse.ArgumentParser(
    parents=[argparse_parent_base.parser],
)

parser.add_argument('--local-arg',
                    action="store_true",
                    default=False)

print(parser.parse_args())

Ed il programma risultante otterrà tutte e tre le opzioni:

$ python3 argparse_uses_parent.py -h

usage: argparse_uses_parent.py [-h] [--user USER] [--password PASSWORD]
                               [--local-arg]

optional arguments:
  -h, --help           show this help message and exit
  --user USER
  --password PASSWORD
  --local-arg
Opzioni in Conflitto

L'esempio precedente evidenziava che l'aggiunta di due gestori di argomenti a un parser usando lo stesso nome di argomento causa una eccezione. Per modificare il comportamento per la risoluzione dei conflitti si passa un gestore di conflitti conflict_handler. I due gestori built-in sono error (il predefinito) e resolve, il quale sceglie un gestore in base all'ordine nel quale sono stati aggiunti.

# argparse_conflict_handler_resolve.py

import argparse

parser = argparse.ArgumentParser(conflict_handler='resolve')

parser.add_argument('-a', action="store")
parser.add_argument('-b', action="store", help='Solo opzione breve')
parser.add_argument('--long-b', '-b',
                    action="store",
                    help='Opzione breve e verbosa insieme')

print(parser.parse_args(['-h']))

Visto che viene usato l'ultimo gestore con uno specifico nome di argomento, in questo esempio l'opzione a carattere singolo -b viene mascherata dall'alias per --long-b.

$ python3 argparse_conflict_handler_resolve.py

usage: argparse_conflict_handler_resolve.py [-h] [-a A] [--long-b LONG_B]

optional arguments:
  -h, --help            show this help message and exit
  -a A
  --long-b LONG_B, -b LONG_B
                        Opzione breve e verbosa insieme

L'inversione dell'ordine delle chiamate a add_argument(), "smaschera" l'opzione a carattere singolo.

# argparse_conflict_handler_resolve.py

import argparse

parser = argparse.ArgumentParser(conflict_handler='resolve')

parser.add_argument('-a', action="store")
parser.add_argument('--long-b', '-b',
                    action="store",
                    help='Opzione breve e verbosa insieme')
parser.add_argument('-b', action="store", help='Solo opzione breve')
print(parser.parse_args(['-h']))

Ora entrambe le opzioni possono essere usate insieme.

$ python3 argparse_conflict_handler_resolve2.py

usage: argparse_conflict_handler_resolve2.py [-h] [-a A] [--long-b LONG_B]
                                             [-b B]

optional arguments:
  -h, --help       show this help message and exit
  -a A
  --long-b LONG_B  Opzione breve e verbosa insieme
  -b B             Solo opzione breve
Gruppi di Argomenti

argparse combina le definizioni degli argomenti in "gruppi". Nella modalità predefinita usa due gruppi, uno per le opzioni e un altro per argomenti richiesti basati sula posizione degli stessi.

# argparse_default_grouping.py

import argparse

parser = argparse.ArgumentParser(description='Piccola semplice app')

parser.add_argument('--opzionale', action="store_true",
                    default=False)
parser.add_argument('posizionale', action="store")

print(parser.parse_args())

Il raggruppamento viene rispecchiato nelle sezioni separate "positional arguments" (argomenti posizionali) ed "optional arguments" (argomenti opzionali) nell'output di aiuto:

$ python3 argparse_default_grouping.py -h

usage: argparse_default_grouping.py [-h] [--opzionale] posizionale

Piccola semplice app

positional arguments:
  posizionale

optional arguments:
  -h, --help   show this help message and exit
  --opzionale

Si può modificare il raggruppamento per renderlo più logico nell'aiuto, in modo che le opzioni o i valori correlati siano documentati assieme. L'esempio di opzioni condivise fatto precedentemente potrebbe essere scritto usando un raggruppamento personalizzato in modo che le opzioni di autenticazione siano mostrate assieme nell'aiuto.

Si crea un gruppo "autenticazione" tramite add_argument_group(), quindi si aggiunge ciascuna delle opzioni legate all'autenticazione al gruppo, invece che al parser di base.

# argparse_parent_with_group.py

import argparse

parser = argparse.ArgumentParser(add_help=False)

group = parser.add_argument_group('autenticazione')

group.add_argument('--user', action="store")
group.add_argument('--password', action="store")

Il programma che usa gli elenchi basati sul gruppo del genitore li elenca nel valore parents, proprio come prima:

# argparse_uses_parent_with_group.py

import argparse
import argparse_parent_with_group

parser = argparse.ArgumentParser(
    parents=[argparse_parent_with_group.parser],
)

parser.add_argument('--locale-arg',
                    action="store_true",
                    default=False)

print(parser.parse_args())

L'output di aiuto ora mostra le opzioni di autenticazione assieme:

$ python3 argparse_uses_parent_with_group.py -h

usage: argparse_uses_parent_with_group.py [-h] [--user USER]
                                          [--password PASSWORD] [--locale-arg]

optional arguments:
  -h, --help           show this help message and exit
  --locale-arg

autenticazione:
  --user USER
  --password PASSWORD
Opzioni a Mutua Esclusione

Una tipologia speciale della caratteristica di raggruppamento è la definizione di opzioni mutualmente esclusive, che si ottiene usando add_mutually_exclusive_group() invece che add_argument_group().

# argparse_mutually_exclusive.py

import argparse

parser = argparse.ArgumentParser()

group = parser.add_mutually_exclusive_group()
group.add_argument('-a', action='store_true')
group.add_argument('-b', action='store_true')

print(parser.parse_args())

argparse si occupa della forzatura della mutua esclusività, in modo che solo una delle opzioni dal gruppo possa essere fornita.

$ python3 argparse_mutually_exclusive.py  -h

usage: argparse_mutually_exclusive.py [-h] [-a | -b]

optional arguments:
  -h, --help  show this help message and exit
  -a
  -b
Annidare i Parser

L'approccio tramite parser "genitore" descritto qui sopra è un modo per condividere opzioni tra comandi correlati. Un approccio alternativo è quello di combinare i comandi in un programma singolo, utilizzando dei "subparser" per gestire ciascuna porzione della riga di comando. Il risultato viene espresso allo stesso modo di programmi come svn, hg e altri con azioni multiple da riga di comando, o sotto comandi.

Un programma che lavori con directory nel file system potrebbe definire dei comandi per creare, eliminare ed elencare il contenuto di una directory.

# argparse_subparsers.py

import argparse

parser = argparse.ArgumentParser()

subparsers = parser.add_subparsers(help='comandi')

# Un comando di elenco
list_parser = subparsers.add_parser('elenca', help='Elenco contenuto')
list_parser.add_argument(
                         'nomedir',
                         action='store', help='Directory da elencare')

# Un comando di creazione
create_parser = subparsers.add_parser('crea', help='Crea una directory')
create_parser.add_argument(
                           'nomedir',
                           action='store', help='Nuova directory da creare')
create_parser.add_argument(
        '--sola-lettura',
        default=False, action='store_true',
        help='Imposta i permessi per impedire la scrittura nella directory')

# Un comando di eliminazione
delete_parser = subparsers.add_parser('elimina', help='Elimina una directory')
delete_parser.add_argument(
                           'nomedir', action='store',
                           help='La directory da eliminare')
delete_parser.add_argument('--ricorsiva', '-r',
                           default=False, action='store_true',
                           help='Elimina anche il contenuto della directory',
                           )

print(parser.parse_args())

L'output di aiuto mostra i "subparse" identificati come "comandi" che possono essere specificati nella riga di comando come argomenti posizionali.

$ python3 argparse_subparsers.py -h

usage: argparse_subparsers.py [-h] {elenca,crea,elimina} ...

positional arguments:
  {elenca,crea,elimina}
                        comandi
    elenca              Elenco contenuto
    crea                Crea una directory
    elimina             Elimina una directory

optional arguments:
  -h, --help            show this help message and exit

Ciascun "subparser" ha anche il proprio aiuto, che descrive gli argomenti e le opzioni per quel comando.

$ python3 argparse_subparsers.py crea -h

usage: argparse_subparsers.py crea [-h] [--sola-lettura] nomedir

positional arguments:
  nomedir         Nuova directory da creare

optional arguments:
  -h, --help      show this help message and exit
  --sola-lettura  Imposta i permessi per impedire la scrittura nella directory

Quando gli argomenti sono elaborati, l'oggetto Namespace ritornato da parse_args() include solo i valori relativi al comando specificato.

$ python3 argparse_subparsers.py elimina -r foo

Namespace(nomedir='foo', ricorsiva=True)

Elaborazione di Argomenti Avanzata

Fino a ora gli esempi hanno mostrato semplici flag booleani, opzioni con argomenti stringa o numerici e argomenti posizionali. argparse supporta anche sofisticate specifiche di argomenti per elenchi di argomenti a lunghezza variabile, enumerazioni e valori costanti.

Elenchi Variabili di Argomenti

E' possibile configurare una singola definizione di argomento per consumare argomenti multipli sulla riga di comando mentre vengono elaborati. Occorre impostare nargs a uno dei flag seguenti, in base al numero di argomenti specificati o attesi.

Valore Significato
N Il numero di argomenti assoluto (es. 3)
? 0 od 1 argomento
* 0 o tutti gli argomenti
+ Tutti o almeno un argomento
# argparse_nargs.py

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('--tre', nargs=3)
parser.add_argument('--opzionale', nargs='?')
parser.add_argument('--tutti', nargs='*', dest='all')
parser.add_argument('--uno-o-piu', nargs='+')


print(parser.parse_args())

Il parser applica le istruzioni di conteggio degli argomenti, e genera un accurato diagramma di sintassi come parte del testo di aiuto del comando.

$ python3 argparse_nargs.py -h

usage: argparse_nargs.py [-h] [--tre TRE TRE TRE] [--opzionale [OPZIONALE]]
                         [--tutti [ALL [ALL ...]]]
                         [--uno-o-piu UNO_O_PIU [UNO_O_PIU ...]]

optional arguments:
  -h, --help            show this help message and exit
  --tre TRE TRE TRE
  --opzionale [OPZIONALE]
  --tutti [ALL [ALL ...]]
  --uno-o-piu UNO_O_PIU [UNO_O_PIU ...]
$ python3 argparse_nargs.py

Namespace(all=None, opzionale=None, tre=None, uno_o_piu=None)
$ python argparse_nargs.py --tre

usage: argparse_nargs.py [-h] [--tre TRE TRE TRE] [--opzionale [OPZIONALE]]
                         [--tutti [ALL [ALL ...]]]
                         [--uno-o-piu UNO_O_PIU [UNO_O_PIU ...]]
argparse_nargs.py: error: argument --tre: expected 3 arguments
$ python3 argparse_nargs.py --tre a b c
</py_output>&
$ python3 argparse_nargs.py --opzionale
$ python3 argparse_nargs.py --opzionale con_valore

Namespace(all=None, opzionale='con_valore', tre=None, uno_o_piu=None)
$ python3 argparse_nargs.py --tutti con valori multipli

Namespace(all=['con', 'valori', 'multipli'], opzionale=None, tre=None, uno_o_piu=None)
$ python3 argparse_nargs.py --uno-o-piu con_valore

Namespace(all=None, opzionale=None, tre=None, uno_o_piu=['con_valore'])
$ python3 argparse_nargs.py --uno-o-piu con valori multipli

Namespace(all=None, opzionale=None, tre=None, uno_o_piu=['con', 'valori', 'multipli'])
$ python3 argparse_nargs.py --uno-o-piu

usage: argparse_nargs.py [-h] [--tre TRE TRE TRE] [--opzionale [OPZIONALE]]
                         [--tutti [ALL [ALL ...]]]
                         [--uno-o-piu UNO_O_PIU [UNO_O_PIU ...]]
argparse_nargs.py: error: argument --uno-o-piu: expected at least one argument
Tipi di Argomento

argparse tratta tutti i valori degli argomenti come stringhe, a meno che non gli si dica di convertire la stringa in altro tipo. Il parametro type di add_argument() definisce una funzione di conversione utilizzata da ArgumentParser per trasformare il valore dell'argomento da stringa a qualche altro tipo.

# argparse_type.py

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-i', type=int)
parser.add_argument('-f', type=float)
parser.add_argument('--file', type=open)

try:
    print(parser.parse_args())
except IOError as msg:
    parser.error(str(msg))

Qualsiasi chiamabile che riceve una stringa singola come argomento può essere passato a type, compresi i tipi built-in come int e float, oppure open().

$ python3 argparse_type.py -i 1

Namespace(f=None, file=None, i=1)
$ python3 argparse_type.py -f 3.14

Namespace(f=3.14, file=None, i=None)
$ python3 argparse_type.py --file argparse_type.py

Namespace(f=None, file=<_io.TextIOWrapper name='argparse_type.py' mode='r' encoding='UTF-8'>, i=None)

Se la conversione di tipo fallisce, argparse solleva eccezioni TypeError e ValueError che sono intercettate automaticamente e convertite in un semplice messaggio di errore per l'utente. Altre eccezioni, tipo IOError nell'esempio qui sotto, sollevata laddove il file di input non esiste, devono essere gestite dal chiamante.

$ python3 argparse_type.py -i a

usage: argparse_type.py [-h] [-i I] [-f F] [--file FILE]
argparse_type.py: error: argument -i: invalid int value: 'a'
$ python3 argparse_type.py -f 3.14.15

usage: argparse_type.py [-h] [-i I] [-f F] [--file FILE]
argparse_type.py: error: argument -f: invalid float value: '3.14.15'
$ python3 argparse_type.py --file file_non_esiste.txt

usage: argparse_type.py [-h] [-i I] [-f F] [--file FILE]
argparse_type.py: error: [Errno 2] No such file or directory: 'file_non_esiste.txt'

Per limitare un argomento in input a un valore all'interno di un insieme predefinito, si utilizza il parametro choices.

# argparse_choices.py

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('--modo', choices=('sola-lettura', 'lettura-scrittura'))

print(parser.parse_args())

Se l'argomento per --modo non è tra quelli consentiti, viene generato un errore e l'elaborazione viene interrotta.

$ python3 argparse_choices.py -h

usage: argparse_choices.py [-h] [--modo {sola-lettura,lettura-scrittura}]

optional arguments:
  -h, --help            show this help message and exit
  --modo {sola-lettura,lettura-scrittura}
$ python3 argparse_choices.py --modo sola-lettura

Namespace(modo='sola-lettura')
$ python argparse_choices.py --modo accodamento

usage: argparse_choices.py [-h] [--modo {sola-lettura,lettura-scrittura}]
argparse_choices.py: error: argument --modo: invalid choice: 'accodamento' (choose from 'sola-lettura', 'lettura-scrittura')
Argomenti di File

Gli oggetti file possono essere istanziati con un solo argomento stringa, tuttavia questo approccio non consente di ottenere un argomento che specifichi il tipo di accesso. FileType fornisce un modo più flessibile per specificare che un argomento dovrebbe essere un file, incluso la modalità di apertura e la dimensione del buffer.

# argparse_FileType.py

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-i',
                    metavar='file-in-entrata', type=argparse.FileType('rt'))
parser.add_argument('-o',
                    metavar='file-in-uscita', type=argparse.FileType('wt'))

try:
    results = parser.parse_args()
    print('File in entrata:', results.i)
    print('File in uscita:', results.o)
except IOError as msg:
    parser.error(str(msg))

Il valore associato al nome dell'argomento è l'handle del file aperto. Il programmatore è responsabile della chiusura del file, quando lo stesso non viene più usato.

$ python3 argparse_FileType.py -h

usage: argparse_FileType.py [-h] [-i file-in-entrata] [-o file-in-uscita]

optional arguments:
  -h, --help          show this help message and exit
  -i file-in-entrata
  -o file-in-uscita
$ python3 argparse_FileType.py -i argparse_FileType.py -o file_temporaneo.txt

File in entrata: <_io.TextIOWrapper name='argparse_FileType.py' mode='rt' encoding='UTF-8'>
File in uscita: <_io.TextIOWrapper name='file_temporaneo.txt' mode='wt' encoding='UTF-8'>
$ python3 argparse_FileType.py -i file_non_esiste.txt

usage: argparse_FileType.py [-h] [-i file-in-entrata] [-o file-in-uscita]
argparse_FileType.py: error: argument -i: can't open 'file_non_esiste.txt': [Errno 2] No such file or directory: 'file_non_esiste.txt'
Azioni Personalizzate

In aggiunta alle azioni built-in definite in precedenza, è possibile definire azioni personalizzate fornendo un oggetto che implementi l'API Action. L'oggetto passato ad add_argument() come action dovrebbe ottenere parametri che descrivono l'argomento che si sta definendo (tutti gli stessi argomenti dati ad add_argument()) e ritornare un oggetto chiamabile che riceve come parametro il parser che sta elaborando gli argomenti, lo spazio dei nomi (namespace) che conserva i risultati dell'elaborazione, il valore (value) dell'argomento sul quale si sta agendo, e la stringa di opzione (option_string) che ha fatto partire l'azione.

Una classe Action viene fornita come punto di partenza per definire nuove azioni. Il costruttore gestisce la definizione degli argomenti, in modo che si debba solamente sovrascrivere __call__() nella sottoclasse.

# argparse_custom_action.py

import argparse


class AzionePersonalizzata(argparse.Action):
    def __init__(self,
                 option_strings,
                 dest,
                 nargs=None,
                 const=None,
                 default=None,
                 type=None,
                 choices=None,
                 required=False,
                 help=None,
                 metavar=None):
        argparse.Action.__init__(self,
                                 option_strings=option_strings,
                                 dest=dest,
                                 nargs=nargs,
                                 const=const,
                                 default=default,
                                 type=type,
                                 choices=choices,
                                 required=required,
                                 help=help,
                                 metavar=metavar,
                                 )
        print('Inizializzazione di AzionePersonalizzata')
        for name, value in sorted(locals().items()):
            if name == 'self' or value is None:
                continue
            print('  {} = {!r}'.format(name, value))
        print()
        return

    def __call__(self, parser, namespace, values, option_string=None):
        print('Elaborazione di AzionePersonalizzata per {}'.format(self.dest))
        print('  parser = {}'.format(id(parser)))
        print('  values = {!r}'.format(values))
        print('  option_string = {!r}'.format(option_string))

        # Si esegue qualche arbitraria elaborazione dei valori in input
        if isinstance(values, list):
            values = [v.upper() for v in values]
        else:
            values = values.upper()
        # Si salvano i risultati nello spazio dei nomi utilizzando
        # la variabile di destinazione passata al costruttore
        setattr(namespace, self.dest, values)
        print()


parser = argparse.ArgumentParser()

parser.add_argument('-a', action=AzionePersonalizzata)
parser.add_argument('-m', nargs='*', action=AzionePersonalizzata)

results = parser.parse_args([
                            '-a', 'value', '-m',
                            'multi-valore', 'valore-posizionale'])
print(results)

Il tipo di valori dipende dal valore di nargs. Se l'argomento consente valori multipli, values sarà una lista anche se contiene un singolo valore.

Il valore di option_string dipende anche dalla specifica originale degli argomenti. Per argomenti richiesti e posizionali option_string è sempre None.

$ python3 argparse_custom_action.py

Inizializzazione di AzionePersonalizzata
  dest = 'a'
  option_strings = ['-a']
  required = False

Inizializzazione di AzionePersonalizzata
  dest = 'm'
  nargs = '*'
  option_strings = ['-m']
  required = False

Elaborazione di AzionePersonalizzata per a
  parser = 140437621938832
  values = 'value'
  option_string = '-a'

Elaborazione di AzionePersonalizzata per m
  parser = 140437621938832
  values = ['multi-valore', 'valore-posizionale']
  option_string = '-m'

Namespace(a='VALUE', m=['MULTI-VALORE', 'VALORE-POSIZIONALE'])

Vedere anche:

argparse
La documentazione della libreria standard per questo modulo.
ConfigParser
Legge e scrive file di configurazione
shlex
Analizza sintassi tipo shell
Note di portabilità per argparse