Qual è il modo migliore per analizzare gli argomenti della riga di comando?
-
09-06-2019 - |
Domanda
Qual è il più semplice, conciso, e la maggior parte flessibile metodo o libreria per l'analisi degli argomenti della riga di comando Python?
Soluzione
Questa risposta suggerisce optparse
che è appropriato per le versioni precedenti di Python.Per Python 2.7 e versioni successive, argparse
sostituisce optparse
.Vedere questa risposta per maggiori informazioni.
Come hanno sottolineato altre persone, è meglio scegliere optparse anziché getopt.getopt è praticamente una mappatura uno a uno delle funzioni standard della libreria C getopt(3) e non è molto facile da usare.
optparse, pur essendo un po' più dettagliato, è molto meglio strutturato e più semplice da estendere in seguito.
Ecco una riga tipica per aggiungere un'opzione al tuo parser:
parser.add_option('-q', '--query',
action="store", dest="query",
help="query string", default="spam")
Praticamente parla da solo;al momento dell'elaborazione, accetterà -q o --query come opzioni, memorizzerà l'argomento in un attributo chiamato query e avrà un valore predefinito se non lo specifichi.È anche autodocumentante nel senso che dichiari l'argomento help (che verrà utilizzato quando eseguito con -h/--help) proprio lì con l'opzione.
Di solito analizzi i tuoi argomenti con:
options, args = parser.parse_args()
Questo, per impostazione predefinita, analizzerà gli argomenti standard passati allo script (sys.argv[1:])
options.query verrà quindi impostato sul valore passato allo script.
Crei un parser semplicemente facendo
parser = optparse.OptionParser()
Queste sono tutte le nozioni di base di cui hai bisogno.Ecco uno script Python completo che mostra questo:
import optparse
parser = optparse.OptionParser()
parser.add_option('-q', '--query',
action="store", dest="query",
help="query string", default="spam")
options, args = parser.parse_args()
print 'Query string:', options.query
5 linee di pitone che ti mostrano le basi.
Salvalo in sample.py ed eseguilo una volta con
python sample.py
e una volta con
python sample.py --query myquery
Oltre a ciò, scoprirai che optparse è molto facile da estendere.In uno dei miei progetti, ho creato una classe Command che ti consente di annidare facilmente i sottocomandi in un albero dei comandi.Utilizza pesantemente optparse per concatenare i comandi.Non è qualcosa che posso spiegare facilmente in poche righe, ma sentitevi liberi di farlo curiosare nel mio repository per la classe principale, nonché una classe che lo utilizza e l'opzione parser
Altri suggerimenti
Altre risposte lo menzionano argparse
è la strada da percorrere per il nuovo Python, ma non fornire esempi di utilizzo.Per completezza, ecco un breve riassunto di come utilizzare argparse:
1) Inizializza
import argparse
# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')
2) Aggiungi argomenti
# Required positional argument
parser.add_argument('pos_arg', type=int,
help='A required integer positional argument')
# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
help='An optional integer positional argument')
# Optional argument
parser.add_argument('--opt_arg', type=int,
help='An optional integer argument')
# Switch
parser.add_argument('--switch', action='store_true',
help='A boolean switch')
3) Analizzare
args = parser.parse_args()
4) Accesso
print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)
5) Controllare i valori
if args.pos_arg > 10:
parser.error("pos_arg cannot be larger than 10")
Utilizzo
Uso corretto:
$ ./app 1 2 --opt_arg 3 --switch
Argument values:
1
2
3
True
Argomentazioni errate:
$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'
$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10
Aiuto completo:
$ ./app -h
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
Optional app description
positional arguments:
pos_arg A required integer positional argument
opt_pos_arg An optional integer positional argument
optional arguments:
-h, --help show this help message and exit
--opt_arg OPT_ARG An optional integer argument
--switch A boolean switch
Utilizzando Docopt
Dal 2012 Python ha un sistema molto semplice, potente e davvero Freddo modulo per l'analisi degli argomenti chiamato docopt.Funziona con Python dalla versione 2.6 alla 3.5 e non necessita di installazione (basta copiarlo).Ecco un esempio tratto dalla sua documentazione:
"""Naval Fate.
Usage:
naval_fate.py ship new <name>...
naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
naval_fate.py ship shoot <x> <y>
naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.py (-h | --help)
naval_fate.py --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__, version='Naval Fate 2.0')
print(arguments)
Quindi è questo:2 righe di codice più la stringa del documento which È essenziale e ottieni i tuoi argomenti analizzati e disponibili nel tuo oggetto argomenti.Te l'avevo detto che è bello, vero ;-)
Usando Python-Fire
Dal 2017 pitone-fuoco ha un altro modulo interessante che può fornire un'interfaccia CLI al tuo codice mentre lo fai zero analisi degli argomenti.Ecco un semplice esempio tratto dalla documentazione (questo piccolo programma espone function double
alla riga di comando):
import fire
class Calculator(object):
def double(self, number):
return 2 * number
if __name__ == '__main__':
fire.Fire(Calculator)
Dalla riga di comando è possibile eseguire:
> calculator.py double 10
20
> calculator.py double --number=15
30
Fantastico, vero?
preferisco Clic.Astrae le opzioni di gestione e consente "(...) di creare bellissime interfacce a riga di comando in modo componibile con il minimo codice necessario".
Ecco un esempio di utilizzo:
import click
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
hello()
Inoltre genera automaticamente pagine di aiuto ben formattate:
$ python hello.py --help
Usage: hello.py [OPTIONS]
Simple program that greets NAME for a total of COUNT times.
Options:
--count INTEGER Number of greetings.
--name TEXT The person to greet.
--help Show this message and exit.
Praticamente tutti lo usano getopt
Ecco il codice di esempio per il documento:
import getopt, sys
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])
except getopt.GetoptError:
# print help information and exit:
usage()
sys.exit(2)
output = None
verbose = False
for o, a in opts:
if o == "-v":
verbose = True
if o in ("-h", "--help"):
usage()
sys.exit()
if o in ("-o", "--output"):
output = a
Quindi, in una parola, ecco come funziona.
Hai due tipi di opzioni.Coloro che ricevono argomenti e quelli che sono proprio come switch.
sys.argv
è praticamente tuo char** argv
in C.Come in C salti il primo elemento che è il nome del tuo programma e analizzi solo gli argomenti: sys.argv[1:]
Getopt.getopt
lo analizzerà in base alla regola fornita in argomento.
"ho:v"
qui vengono descritti i brevi argomenti: -ONELETTER
.IL :
significa che -o
accetta un argomento.
Finalmente ["help", "output="]
descrive argomenti lunghi ( --MORETHANONELETTER
).IL =
dopo l'output significa ancora una volta che l'output accetta un argomento.
Il risultato è un elenco di coppie (opzione, argomento)
Se un'opzione non accetta alcun argomento (come --help
qui) il arg
part è una stringa vuota.Quindi di solito vuoi eseguire il loop su questo elenco e testare il nome dell'opzione come nell'esempio.
Spero che questo ti abbia aiutato.
Utilizzo optparse
che viene fornito con la libreria standard.Per esempio:
#!/usr/bin/env python
import optparse
def main():
p = optparse.OptionParser()
p.add_option('--person', '-p', default="world")
options, arguments = p.parse_args()
print 'Hello %s' % options.person
if __name__ == '__main__':
main()
Fonte: Utilizzo di Python per creare strumenti da riga di comando UNIX
Tuttavia, a partire da Python 2.7 optparse è deprecato, vedere: Perché usare argparse anziché optparse?
Nel caso in cui ne avessi bisogno, questo potrebbe aiutarti se necessario preda argomenti unicode su Win32 (2K, XP ecc.):
from ctypes import *
def wmain(argc, argv):
print argc
for i in argv:
print i
return 0
def startup():
size = c_int()
ptr = windll.shell32.CommandLineToArgvW(windll.kernel32.GetCommandLineW(), byref(size))
ref = c_wchar_p * size.value
raw = ref.from_address(ptr)
args = [arg for arg in raw]
windll.kernel32.LocalFree(ptr)
exit(wmain(len(args), args))
startup()
Impostazioni predefinite degli argomenti leggeri della riga di comando
Sebbene argparse
è fantastico ed è la risposta giusta per opzioni della riga di comando completamente documentate e funzionalità avanzate, puoi utilizzare i valori predefiniti degli argomenti delle funzioni per gestire argomenti posizionali semplici in modo molto semplice.
import sys
def get_args(name='default', first='a', second=2):
return first, int(second)
first, second = get_args(*sys.argv)
print first, second
L'argomento 'name' cattura il nome dello script e non viene utilizzato.L'output del test è simile al seguente:
> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20
Per script semplici in cui desidero solo alcuni valori predefiniti, lo trovo abbastanza sufficiente.Potresti anche voler includere qualche coercizione del tipo nei valori restituiti altrimenti i valori della riga di comando saranno tutti stringhe.
Penso che il modo migliore per progetti più grandi sia optparse, ma se stai cercando un modo semplice, forse http://werkzeug.pocoo.org/documentation/script è qualcosa per te.
from werkzeug import script
# actions go here
def action_foo(name=""):
"""action foo does foo"""
pass
def action_bar(id=0, title="default title"):
"""action bar does bar"""
pass
if __name__ == '__main__':
script.run()
Quindi fondamentalmente ogni funzione Action_* è esposta alla riga di comando e viene generato un messaggio di buon aiuto gratuitamente.
python foo.py
usage: foo.py <action> [<options>]
foo.py --help
actions:
bar:
action bar does bar
--id integer 0
--title string default title
foo:
action foo does foo
--name string
Preferisco optparse a getopt.È molto dichiarativo:gli dici i nomi delle opzioni e gli effetti che dovrebbero avere (ad esempio, impostando un campo booleano) e ti restituisce un dizionario popolato secondo le tue specifiche.
consoleargs merita di essere menzionato qui.È molto facile da usare.Controlla:
from consoleargs import command
@command
def main(url, name=None):
"""
:param url: Remote URL
:param name: File name
"""
print """Downloading url '%r' into file '%r'""" % (url, name)
if __name__ == '__main__':
main()
Ora in console:
% python demo.py --help
Usage: demo.py URL [OPTIONS]
URL: Remote URL
Options:
--name -n File name
% python demo.py http://www.google.com/
Downloading url ''http://www.google.com/'' into file 'None'
% python demo.py http://www.google.com/ --name=index.html
Downloading url ''http://www.google.com/'' into file ''index.html''
Il codice Argparse può essere più lungo del codice di implementazione effettivo!
Questo è un problema che trovo con le opzioni di analisi degli argomenti più popolari è che se i tuoi parametri sono solo modesti, il codice per documentarli diventa sproporzionatamente grande rispetto al vantaggio che forniscono.
Un relativamente nuovo arrivato nella scena di analisi degli argomenti (credo) è posto.
Fa alcuni compromessi riconosciuti con argparse, ma utilizza la documentazione in linea e si avvolge semplicemente main()
tipo funzione funzione:
def main(excel_file_path: "Path to input training file.",
excel_sheet_name:"Name of the excel sheet containing training data including columns 'Label' and 'Description'.",
existing_model_path: "Path to an existing model to refine."=None,
batch_size_start: "The smallest size of any minibatch."=10.,
batch_size_stop: "The largest size of any minibatch."=250.,
batch_size_step: "The step for increase in minibatch size."=1.002,
batch_test_steps: "Flag. If True, show minibatch steps."=False):
"Train a Spacy (http://spacy.io/) text classification model with gold document and label data until the model nears convergence (LOSS < 0.5)."
pass # Implementation code goes here!
if __name__ == '__main__':
import plac; plac.call(main)
Ecco un metodo, non una libreria, che sembra funzionare per me.
Gli obiettivi qui devono essere concisi, ogni argomento analizzato da una singola riga, gli argomenti allineati per la leggibilità, il codice è semplice e non dipende da moduli speciali (solo os + sys), avverte con garbo su argomenti mancanti o sconosciuti , usa un semplice ciclo for/range() e funziona su Python 2.xe 3.x
Vengono mostrati due flag di attivazione/disattivazione (-d, -v) e due valori controllati da argomenti (-i xxx e -o xxx).
import os,sys
def HelpAndExit():
print("<<your help output goes here>>")
sys.exit(1)
def Fatal(msg):
sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
sys.exit(1)
def NextArg(i):
'''Return the next command line argument (if there is one)'''
if ((i+1) >= len(sys.argv)):
Fatal("'%s' expected an argument" % sys.argv[i])
return(1, sys.argv[i+1])
### MAIN
if __name__=='__main__':
verbose = 0
debug = 0
infile = "infile"
outfile = "outfile"
# Parse command line
skip = 0
for i in range(1, len(sys.argv)):
if not skip:
if sys.argv[i][:2] == "-d": debug ^= 1
elif sys.argv[i][:2] == "-v": verbose ^= 1
elif sys.argv[i][:2] == "-i": (skip,infile) = NextArg(i)
elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
elif sys.argv[i][:2] == "-h": HelpAndExit()
elif sys.argv[i][:1] == "-": Fatal("'%s' unknown argument" % sys.argv[i])
else: Fatal("'%s' unexpected" % sys.argv[i])
else: skip = 0
print("%d,%d,%s,%s" % (debug,verbose,infile,outfile))
L'obiettivo di NextArg() è restituire l'argomento successivo durante il controllo dei dati mancanti e 'skip' salta il ciclo quando viene utilizzato NextArg(), mantenendo il flag analizzato su una riga.
Ho esteso l'approccio di Erco per consentire argomenti posizionali obbligatori e argomenti facoltativi.Questi dovrebbero precedere -d, -v ecc.argomenti.
Gli argomenti posizionali e facoltativi possono essere recuperati rispettivamente con PosArg(i) e OptArg(i, default).Quando viene trovato un argomento facoltativo, la posizione iniziale della ricerca delle opzioni (ad es.-i) viene spostato di 1 avanti per evitare di causare un fatale 'inaspettato'.
import os,sys
def HelpAndExit():
print("<<your help output goes here>>")
sys.exit(1)
def Fatal(msg):
sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
sys.exit(1)
def NextArg(i):
'''Return the next command line argument (if there is one)'''
if ((i+1) >= len(sys.argv)):
Fatal("'%s' expected an argument" % sys.argv[i])
return(1, sys.argv[i+1])
def PosArg(i):
'''Return positional argument'''
if i >= len(sys.argv):
Fatal("'%s' expected an argument" % sys.argv[i])
return sys.argv[i]
def OptArg(i, default):
'''Return optional argument (if there is one)'''
if i >= len(sys.argv):
Fatal("'%s' expected an argument" % sys.argv[i])
if sys.argv[i][:1] != '-':
return True, sys.argv[i]
else:
return False, default
### MAIN
if __name__=='__main__':
verbose = 0
debug = 0
infile = "infile"
outfile = "outfile"
options_start = 3
# --- Parse two positional parameters ---
n1 = int(PosArg(1))
n2 = int(PosArg(2))
# --- Parse an optional parameters ---
present, a3 = OptArg(3,50)
n3 = int(a3)
options_start += int(present)
# --- Parse rest of command line ---
skip = 0
for i in range(options_start, len(sys.argv)):
if not skip:
if sys.argv[i][:2] == "-d": debug ^= 1
elif sys.argv[i][:2] == "-v": verbose ^= 1
elif sys.argv[i][:2] == "-i": (skip,infile) = NextArg(i)
elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
elif sys.argv[i][:2] == "-h": HelpAndExit()
elif sys.argv[i][:1] == "-": Fatal("'%s' unknown argument" % sys.argv[i])
else: Fatal("'%s' unexpected" % sys.argv[i])
else: skip = 0
print("Number 1 = %d" % n1)
print("Number 2 = %d" % n2)
print("Number 3 = %d" % n3)
print("Debug = %d" % debug)
print("verbose = %d" % verbose)
print("infile = %s" % infile)
print("outfile = %s" % outfile)