Quelle est la meilleure façon d’analyser les arguments de ligne de commande ?
-
09-06-2019 - |
Question
Quel est le le plus simple, laconique, et plus flexible méthode ou bibliothèque pour analyser les arguments de ligne de commande Python ?
La solution
Cette réponse suggère optparse
ce qui convient aux anciennes versions de Python.Pour Python 2.7 et supérieur, argparse
remplace optparse
.Voir cette réponse pour plus d'informations.
Comme d'autres personnes l'ont souligné, il vaut mieux utiliser optparse plutôt que getopt.getopt est à peu près un mappage un à un des fonctions standard de la bibliothèque getopt(3) C, et pas très facile à utiliser.
optparse, tout en étant un peu plus verbeux, est bien mieux structuré et plus simple à étendre ultérieurement.
Voici une ligne typique pour ajouter une option à votre analyseur :
parser.add_option('-q', '--query',
action="store", dest="query",
help="query string", default="spam")
Cela parle de lui-même ;au moment du traitement, il acceptera -q ou --query comme options, stockera l'argument dans un attribut appelé query et aura une valeur par défaut si vous ne la spécifiez pas.Il est également auto-documenté dans la mesure où vous déclarez l'argument help (qui sera utilisé lors de l'exécution avec -h/--help) ici même avec l'option.
Habituellement, vous analysez vos arguments avec :
options, args = parser.parse_args()
Cela analysera, par défaut, les arguments standard passés au script (sys.argv[1:])
options.query sera alors défini sur la valeur que vous avez transmise au script.
Vous créez un analyseur simplement en faisant
parser = optparse.OptionParser()
Ce sont toutes les bases dont vous avez besoin.Voici un script Python complet qui montre ceci :
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 lignes de python qui vous montrent les bases.
Enregistrez-le dans sample.py et exécutez-le une fois avec
python sample.py
et une fois avec
python sample.py --query myquery
Au-delà de cela, vous constaterez qu’optparse est très facile à étendre.Dans l'un de mes projets, j'ai créé une classe Command qui vous permet d'imbriquer facilement des sous-commandes dans une arborescence de commandes.Il utilise beaucoup optparse pour enchaîner les commandes.Ce n'est pas quelque chose que je peux facilement expliquer en quelques lignes, mais n'hésitez pas à parcourir mon référentiel pour la classe principale, ainsi que une classe qui l'utilise et l'analyseur d'options
Autres conseils
D'autres réponses mentionnent que argparse
est la voie à suivre pour le nouveau Python, mais ne donne pas d'exemples d'utilisation.Pour être complet, voici un bref résumé de la façon d'utiliser argparse :
1) Initialiser
import argparse
# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')
2) Ajouter des arguments
# 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) Analyser
args = parser.parse_args()
4) Accès
print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)
5) Vérifier les valeurs
if args.pos_arg > 10:
parser.error("pos_arg cannot be larger than 10")
Usage
Utilisation correcte :
$ ./app 1 2 --opt_arg 3 --switch
Argument values:
1
2
3
True
Arguments incorrects :
$ ./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
Aide complète :
$ ./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
Utiliser Docopt
Depuis 2012, Python dispose d'un outil très simple, puissant et vraiment cool module d'analyse des arguments appelé docopt.Il fonctionne avec Python 2.6 à 3.5 et ne nécessite aucune installation (il suffit de le copier).Voici un exemple tiré de sa documentation :
"""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)
Alors c'est ça:2 lignes de code plus votre chaîne doc qui est essentiel et vous obtenez vos arguments analysés et disponibles dans votre objet arguments.Je t'ai dit que c'était cool, n'est-ce pas ;-)
Utiliser python-fire
Depuis 2017 python-feu a un autre module sympa qui peut donner une interface CLI à votre code avec vous zéro analyse des arguments.Voici un exemple simple tiré de la documentation (ce petit programme expose la fonction double
à la ligne de commande):
import fire
class Calculator(object):
def double(self, number):
return 2 * number
if __name__ == '__main__':
fire.Fire(Calculator)
Depuis la ligne de commande, vous pouvez exécuter :
> calculator.py double 10
20
> calculator.py double --number=15
30
Génial, n'est-ce pas ?
je préfère Cliquez sur.Il résume les options de gestion et permet de "(...) créer de belles interfaces de ligne de commande de manière composable avec aussi peu de code que nécessaire".
Voici un exemple d'utilisation :
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()
Il génère également automatiquement des pages d'aide bien formatées :
$ 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.
Presque tout le monde utilise optez pour
Voici l'exemple de code pour la doc :
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
En un mot, voici comment cela fonctionne.
Vous avez deux types d'options.Ceux qui reçoivent des arguments et ceux qui sont comme des commutateurs.
sys.argv
c'est à peu près ton char** argv
en C.Comme en C, vous sautez le premier élément qui est le nom de votre programme et analysez uniquement les arguments : sys.argv[1:]
Getopt.getopt
l'analysera selon la règle que vous donnez en argument.
"ho:v"
décrit ici les courts arguments : -ONELETTER
.Le :
signifie que -o
accepte un argument.
Enfin ["help", "output="]
décrit de longs arguments ( --MORETHANONELETTER
).Le =
après la sortie, cela signifie à nouveau que la sortie accepte un argument.
Le résultat est une liste de couples (option,argument)
Si une option n'accepte aucun argument (comme --help
ici le arg
part est une chaîne vide.Vous souhaitez ensuite généralement parcourir cette liste et tester le nom de l'option comme dans l'exemple.
J'espère que cela vous a aidé.
Utiliser optparse
qui vient avec la bibliothèque standard.Par exemple:
#!/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()
Source: Utiliser Python pour créer des outils de ligne de commande UNIX
Cependant, depuis Python 2.7, optparse est obsolète, voir : Pourquoi utiliser argparse plutôt que optparse ?
Juste au cas où vous en auriez besoin, cela peut vous aider si vous en avez besoin saisir Arguments Unicode sur Win32 (2K, XP etc):
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()
Valeurs par défaut des arguments de ligne de commande légers
Bien que argparse
est génial et constitue la bonne réponse pour les commutateurs de ligne de commande entièrement documentés et les fonctionnalités avancées, vous pouvez utiliser les valeurs par défaut des arguments de fonction pour gérer très simplement les arguments de position simples.
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'argument 'name' capture le nom du script et n'est pas utilisé.Le résultat du test ressemble à ceci :
> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20
Pour les scripts simples où je veux juste quelques valeurs par défaut, je trouve cela tout à fait suffisant.Vous souhaiterez peut-être également inclure une certaine coercition de type dans les valeurs de retour ou les valeurs de ligne de commande seront toutes des chaînes.
Je pense que le meilleur moyen pour les projets plus importants est optparse, mais si vous cherchez un moyen simple, peut-être http://werkzeug.pocoo.org/documentation/script est quelque chose pour vous.
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()
Donc, fondamentalement, chaque fonction Action_ * est exposée à la ligne de commande et un bon message d'aide est généré gratuitement.
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
Je préfère optparse à getopt.C'est très déclaratif :vous lui indiquez les noms des options et les effets qu'elles devraient avoir (par exemple, définir un champ booléen), et il vous rend un dictionnaire rempli selon vos spécifications.
consoleargs mérite d'être mentionné ici.Il est très simple à utiliser.Vérifiez-le:
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()
Maintenant en 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''
Le code Argparse peut être plus long que le code d’implémentation réel !
C'est un problème que je trouve avec les options d'analyse d'arguments les plus populaires, c'est que si vos paramètres ne sont que modestes, le code pour les documenter devient disproportionné par rapport aux avantages qu'ils apportent.
Un nouveau venu sur la scène de l'analyse des arguments (je pense) est place.
Il fait des compromis reconnus avec argparse, mais utilise la documentation en ligne et s'enroule simplement autour de main()
tapez la fonction fonction :
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)
Voici une méthode, pas une bibliothèque, qui semble fonctionner pour moi.
Les objectifs ici sont d'être concis, chaque argument analysé par une seule ligne, les arguments s'alignent pour plus de lisibilité, le code est simple et ne dépend d'aucun module spécial (uniquement os + sys), avertit gracieusement des arguments manquants ou inconnus. , utilisez une simple boucle for/range() et fonctionne sur python 2.x et 3.x
Deux indicateurs à bascule (-d, -v) et deux valeurs contrôlées par des arguments (-i xxx et -o xxx) sont affichés.
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))
Le but de NextArg() est de renvoyer l'argument suivant tout en vérifiant les données manquantes, et 'skip' saute la boucle lorsque NextArg() est utilisé, en gardant l'indicateur analysé à une seule ligne.
J'ai étendu l'approche d'Erco pour permettre les arguments positionnels requis et les arguments facultatifs.Ceux-ci doivent précéder les -d, -v etc.arguments.
Les arguments positionnels et facultatifs peuvent être récupérés respectivement avec PosArg(i) et OptArg(i, default).Lorsqu'un argument facultatif est trouvé, la position de départ de la recherche d'options (par ex.-i) est avancé de 1 pour éviter de provoquer un décès « inattendu ».
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)