Quelle est la meilleure façon d'analyser les arguments de la ligne de commande? [fermé]

251

Quelle est la méthode ou la bibliothèque la plus simple , la plus simple et la plus flexible pour analyser les arguments de ligne de commande Python?

kamens
la source

Réponses:

183

Cette réponse suggère celle optparsequi convient aux anciennes versions de Python. Pour Python 2.7 et supérieur, argparseremplace optparse. Voir cette réponse pour plus d'informations.

Comme d'autres l'ont souligné, il vaut mieux opter pour 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 beaucoup mieux structuré et plus simple à étendre plus tard.

Voici une ligne typique pour ajouter une option à votre analyseur:

parser.add_option('-q', '--query',
            action="store", dest="query",
            help="query string", default="spam")

Il parle à peu près 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 le spécifiez pas. Il est également auto-documenté en ce que vous déclarez l'argument d'aide (qui sera utilisé lorsqu'il sera exécuté avec -h / - help) directement avec l'option.

Habituellement, vous analysez vos arguments avec:

options, args = parser.parse_args()

Par défaut, cela analysera les arguments standard transmis 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 que l'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 largement 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 qu'une classe qui l'utilise et l'option analyseur

Thomas Vander Stichele
la source
9
Cette réponse est merveilleusement claire et facile à suivre - pour python 2.3 à 2.6. Pour python 2.7+, ce n'est pas la meilleure réponse car argparse fait maintenant partie de la bibliothèque standard et optparse est déconseillé.
matt wilkie
Dans mon cas, je souhaite profiler mon application pour détecter la lenteur. Il existe un autre outil appelé [thon] ( github.com/nschloe/tuna ) qui me permet de profiler l'application entière en ajoutant simplement agrs -mcProfile -o program.profmais agrparcer capture ces arguments, comment passer ces arguments à exe python ???
Yogeshwar
231

argparseest la voie à suivre. Voici un bref résumé de son utilisation:

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) Valeurs de contrôle

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
Andrzej Pronobis
la source
10
C'est très concis et utile et voici le document officiel (pour plus de commodité): docs.python.org/3/library/argparse.html
Christophe Roussy
1
Si vous trouvez argparse trop verbeux, utilisez plutôt plac .
Nimitz14
76

Utilisation de docopt

Depuis 2012, il existe un module d'analyse des arguments très simple, puissant et vraiment cool appelé docopt . 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 voilà: 2 lignes de code plus votre chaîne doc qui est essentielle et vous obtenez vos arguments analysés et disponibles dans votre objet arguments.

Utiliser python-fire

Depuis 2017, il existe un autre module sympa appelé python-fire . Il peut générer une interface CLI pour votre code avec vous en n'effectuant aucune analyse d'argument. Voici un exemple simple 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
ndemou
la source
4
comment docopt "ne nécessite aucune installation"? c'est un module python donc il doit être installé. «ImportError: aucun module nommé docopt»
vif
1
@keen ce n'est pas inclus avec python à coup sûr mais vous n'avez pas besoin de l'installer: "vous pouvez simplement déposer le fichier docopt.py dans votre projet - il est autonome" - github.com/docopt/docopt
ndemou
9
nous avons juste différentes définitions de l'installation - et je voulais le signaler aux futurs lecteurs.
vif
1
@keen J'ai ajouté une note sur "pas d'installation" pour les personnes partageant votre définition :-)
ndemou
39

La nouvelle façon de hanche est argparsepour ces raisons. argparse> optparse> getopt

mise à jour: à partir de py2.7 argparse fait partie de la bibliothèque standard et optparse est déconseillé.

Silfheed
la source
Votre lien principal est le 404, je l'ai donc remplacé par un lien vers une question SO qui traite du même sujet.
Joe Holloway
28

Je préfère cliquer . 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.
suda
la source
14

Presque tout le monde utilise Presque getopt

Voici l'exemple de code pour le document:

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

Donc 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.argvest à peu près votre char** argven C. Comme en C vous sautez le premier élément qui est le nom de votre programme et n'analysez que les arguments:sys.argv[1:]

Getopt.getopt l'analysera selon la règle que vous donnez en argument.

"ho:v"ici décrit les courts arguments: -ONELETTER. Les :moyens qui-o accepte un argument.

["help", "output="]Décrit enfin de longs arguments ( --MORETHANONELETTER). le= sortie après signifie une fois de plus que la sortie accepte un argument.

Le résultat est une liste de couple (option, argument)

Si une option n'accepte aucun argument (comme --helpici),arg partie est une chaîne vide. Vous souhaitez ensuite généralement faire une boucle sur cette liste et tester le nom de l'option comme dans l'exemple.

J'espère que cela vous a aidé.

fulmicoton
la source
6
Avec la dépréciation de getoptdans les versions plus récentes de Python, cette réponse est obsolète.
shuttle87
1
@ shuttle87 A partir de python3.7.2, getoptn'est toujours pas déprécié… Mais sa documentation indique qu'il est principalement fourni pour les utilisateurs familiers avec la getopt()fonction C , et reconnaît que pour d'autres utilisateurs argparsepourrait être une meilleure solution, permettant d'écrire moins de code et d'obtenir meilleure aide et messages d'erreur ".
Skippy le Grand Gourou
14

Utilisation optparsefournie 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: Utilisation de Python pour créer des outils de ligne de commande UNIX

Cependant, à partir de Python 2.7, l'optparparse est obsolète, voir: Pourquoi utiliser argparse plutôt qu'optparse?

Corey
la source
6

Juste au cas où vous en auriez besoin, cela peut aider si vous avez besoin de récupérer des 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()
Shadow2531
la source
Je vous remercie. Ce script m'a aidé à trouver des citations vraiment compliquées que je devais faire lors du passage des commandes de démarrage à GVim.
telotortium
6

Paramètres d'argument de ligne de commande légers par défaut

Bien qu'il argparsesoit excellent et constitue la bonne réponse pour les commutateurs de ligne de commande et les fonctionnalités avancées entièrement documentés, 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 'nom' capture le nom du script et n'est pas utilisé. La sortie de 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 des valeurs par défaut, je trouve cela tout à fait suffisant. Vous pouvez également vouloir inclure une certaine contrainte de type dans les valeurs de retour ou les valeurs de ligne de commande seront toutes des chaînes.

Simon Hibbs
la source
2
les guillemets ne correspondent pas dans l'instruction def.
historystamp
3

Je préfère optparse à getopt. C'est très déclaratif: vous lui dites les noms des options et les effets qu'elles devraient avoir (par exemple, en définissant un champ booléen), et il vous remet un dictionnaire rempli selon vos spécifications.

http://docs.python.org/lib/module-optparse.html

Chris Conway
la source
3

Je pense que le meilleur moyen pour des projets plus importants est l'optparse, mais si vous cherchez un moyen simple, http://werkzeug.pocoo.org/documentation/script est peut-être 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 joli 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
Peter Hoffmann
la source
J'ai développé un petit paquet en utilisant la création d'arguments automatique: declarative_parser. Bien sûr, si l'on travaille avec werkzeug, il vaut peut-être mieux garder le werkzung.script. Quoi qu'il en soit, je suis un grand fan d'une telle approche.
krassowski
3

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 disproportionnellement grand au profit qu'ils offrent.

Un nouveau venu relatif à la scène d'analyse des arguments (je pense) est plac .

Il fait des compromis reconnus avec argparse, mais utilise la documentation en ligne et s'enroule simplement autour de la main()fonction fonction de type:

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)
Collectif QA
la source
Point d'information: l'utilisation la plus soignée de plac (comme indiqué dans l'exemple) est uniquement pour Python 3.x, car il utilise des annotations de fonction 3.x.
barny
1

consoleargs mérite d'être mentionné ici. C'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 dans la 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''
tige
la source
J'ai utilisé une approche similaire dans déclarative-parser , voir la déduction des arguments (typage, docstrings, kwargs) dans les documents. Différences principales: python3, indices de type, installable par pip.
krassowski
1
Dernier commit en 2012
Boris
0

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 la lisibilité, le code est simple et ne dépend d'aucun module spécial (uniquement os + sys), met en garde contre les arguments manquants ou inconnus avec élégance , utilisez une simple boucle for / range () et fonctionne sur python 2.x et 3.x

Sont affichés deux indicateurs de bascule (-d, -v) et deux valeurs contrôlées par des arguments (-i xxx et -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))

Le but de NextArg () est de renvoyer l'argument suivant tout en vérifiant les données manquantes, et «ignorer» saute la boucle lorsque NextArg () est utilisé, en gardant le drapeau analysé sur une seule ligne.

erco
la source
0

J'ai étendu l'approche d'Erco pour permettre les arguments positionnels requis et les arguments optionnels. Ceux-ci doivent précéder les arguments -d, -v etc.

Les arguments positionnels et facultatifs peuvent être récupérés avec PosArg (i) et OptArg (i, par défaut) respectivement. Lorsqu'un argument facultatif est trouvé, la position de départ de la recherche d'options (par exemple, -i) est avancée de 1 pour éviter de provoquer un incident fatal "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) 
Erik
la source