La plus belle manière de parser les arguments de script en python


On peut parser sys.argv à la main, mais c’est fragile.

On peut utiliser getopt ou optpase, mais c’est déprécié.

On peut adopter le nouveau module de la stdlib, vers argparse, mais c’est compliqué.

Alors j’installais toujours clize.

Mais depuis que j’ai rencontré docopt, j’ai largué clize (c’est pas moi chérie, c’est toi) et on vit en amoureux tous les deux en élevant des scripts dans une ferme dans les Alpes.

pip install docopt

Docopt fonctionne à l’envers de la plupart des libs de ce genre : vous écrivez le message d’aide de votre script, et ça génère le parsing des arguments. La syntaxe de l’aide est de type posix, donc si vous avez utilisez un -h ou la commande man un jour, vous la connaissez déjà.

Voici comment ça marche :

help = """Le nom de mon programme trop cool
 
Usage:
  nom_du_script.py <argument_positionel> [<argument_positionel_optionel>] [--flag-optionel]
 
Options:
  -h --help          C'est généré automatiquement.
  --option=<valeur>  Description de l'option.
 
Woot, un footer !
"""
 
from docopt import docopt
 
arguments = docopt(help)
print(arguments)

Si on passe rien de valide au script, l’usage est affiché automatiquement :

$ python nom_du_script.py 
Usage:
  nom_du_script.py <argument_positionel> [<argument_positionel_optionel>] [--flag-optionel]

Si on demande l’aide, en option, ben, l’aide quoi :

$ python nom_du_script.py -h
Le nom de mon programme trop cool
 
Usage:
  nom_du_script.py <argument_positionel> [<argument_positionel_optionel>] [--flag-optionel]
 
Options:
  -h --help          C'est généré automatiquement.
  --option=<valeur>  Description de l'option.
 
Woot, un footer !

Et si on passe des arguments, on les récupère dans un simple dictionnaire :

$ python nom_du_script.py yo
{'--flag-optionel': False,
 '<argument_positionel>': 'yo',
 '<argument_positionel_optionel>': None}
 
$ python nom_du_script.py yo man
{'--flag-optionel': False,
 '<argument_positionel>': 'yo',
 '<argument_positionel_optionel>': 'man'}
 
 $ python nom_du_script.py yo --flag-optionel
{'--flag-optionel': True,
 '<argument_positionel>': 'yo',
 '<argument_positionel_optionel>': None}

Ça peut générer des choses complexes avec des tas de combinaisons d’options, des sous-commandes et tout le bordel.

Généralement on en profite pour faire ça proprement, en mettant l’usage en docstring du script, en calant une version et en rajoutant un if __main__:

 
"""Uber script.
 
Usage:
  schnell.py scheisse 
  schnell.py bier 
 
Options:
  -h --help      _o/
  --version      \o_
  --blitz=krieg  \o/
 
"""
 
from docopt import docopt
 
 
if __name__ == '__main__':
    # __doc__ contient automatiquement la docstring du module
    # en cours
    arguments = docopt(__doc__, version='0.1')
    print(arguments)
$ python schnell.py --version
0.1
 
$ python schnell.py bier
{'bier': True,
 'scheisse': False}

23 thoughts on “La plus belle manière de parser les arguments de script en python

  • furankun

    C’est chiant; je suis sûr et certain que ça peut m’être utile, mais je n’arrive pas à comprendre comment ni pourquoi. En attendant, typos:
    on option -> en option
    on les récupères -> on les récupère
    en callant -> en calant

  • Sam Post author

    Quand tu voudras faire des scripts configurables, ça t’évitera de galérer à parser les options, c’est tout. Scripts de sysadmin, migration de base de données, etc.

  • foxmask

    t’as l’air fin si tu fais :

    python scriptatouille.py -h

    puisque ca va donner

    Usage:
    schnell.py scheisse
    schnell.py bier

    or tu tapes pas python schnell.py mais python scriptatouille.py :) allez => raus :)

  • cym13

    J’aimerais rajouter que même si c’est en python que le mariage est le mieux réussi à mon avis, docopt existe également pour beaucoup d’autres langages comme ruby, le php, le C, le C++ et même le bash !

    Voir le github du projet docopt pour plus d’informations.

    (Et la video de présentation est trop bien !)

  • Lhassa

    T’écris la doc, ça génère le code… plus aucune raison d’éviter les docstring…

    à cause de vous, des fois, je me demande si j’utilise vos trucs dans mes codes, ou si je code juste pour avoir le plaisir d’utiliser vos trucs..

    en tout cas, merci pour tout et continuez!

  • Sam Post author

    Ca me fait aussi cet effet. Souvent je code juste pour tester un nouveau joujou, et paf, merde alors, j’ai crée un nouveau projet.

  • kontre

    J’aime bien argparse moi, comme je fais toujours des scripts simples j’ai jamais eu de souci. Et c’est très proche de optparse (pour mon utilisation en tout cas). Et puis on peut lui indiquer le type de données qu’il accepte pour une option, donc si je demande un int j’aurai un int (et pas une string à parser), et si c’est pas un int qui est passé ça geulera tout seul à l’exécution en affichant l’aide.

    Cela dit le concept de ce truc est intéressant.

  • titi

    Perso je suis fan et totalement addicte mais j’aimerais pouvoir mettre le script en background avec mon “&” une idée?

  • titi

    En fait un nohup fait l’affaire mais quand même je trouve ma question intéressante :p

  • nohup

    Bon alors personne? Pas une idée brillante par ici?

    Ok visiblement les types de ce bog préfère visiblement se “tater la tentacule en plastique en criant Yameru, Kyōju-Sama !”.

  • Sam Post author

    Alors dans l’ordre :

    – Tu expliques la moitié de ton problème visiblement puisque avec ce script:

    """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
     
    import time
     
    if __name__ == '__main__':
        arguments = docopt(__doc__, version='Naval Fate 2.0')
        print(arguments)
        for x in range(2):
          time.sleep(1)
          print(x)

    Et en lançant :

    python essai.py ship new test &

    Ca se met bien en arrière plan, je viens de le tester.

    Pour t’aider il faudrait donc que tu suives ceci.

    – Il s’agit d’un problème lié à bash, pas particulièrement à cette lib. Tu viens donc raler au mauvais endroit. Qui plus est en comment plutôt que sur un forum d’aide.

    – En prime, tu demandes de l’aide en insultant les gens.

    Bref, tu mérite un tampon.

  • tampon

    Merci! Cool mon premier tampon! Je ne voulais pas être insultante je m’excuse si c’était mal perçu, ca n’était pas mon intention…je voulais juste rire un peu en faisant un clin d’oeil sur votre dernier avant-dernier article

  • Sam Post author

    Soyons honnête, je préfère me tater la tentacule à bien des choses. Pardonnée, mais le tampon reste, parce que j’aime les tampons.

  • Rififi

    Vraiment très cool, comme toujours, c’est ce que je cherchais. Par contre (je sais que c’est relou), ce serait cool du coup que vous mettiez à jour les articles comme “Sept petites libs qui changent la vie d’un dev Python”. Les gens peuvent tomber dessus, commencer avec clize, avant de tomber sur ce nouvel article qui parle de docopt. Enfin c’est juste une suggestion…

  • Ninit

    Merci pour cet article qui m’a permis de découvrir et apprécier docopt. Encore une lecture intéressante chez Sam&Max !

    Ce petit mot pour vous signaler ConfigArgParse> qui est moins élégant et facile à utiliser mais qui vient avec la gestion automatique des arguments dans un fichier de configuration et dans l’environnement.

  • Ninit

    Je me suis mélangé dans le lien du comment précédent : le lien pointe vers configargparse.

  • maxime

    Bonjour,

    Tout d’abord merci beaucoup pour tous ces articles. Ils sont d’une grande qualité et c’est mon go-to en informatique.

    J’ai effectué pas mal de recherche à propos de deux sujets en lien avec cet article et j’avoue ne pas avoir trouvé mon bonheur sur le net. Je profite de ce post pour demander de l’aide à une âme charitable.

    Le premier serait la notation utilisée pour décrire l’usage de scripts/commandes/fonctions dans les documents techniques

    et également dans les docstrings. Dans l’exemple de Sam, “Naval Fate” contient

     
    """Naval Fate.
     
    Usage:
     
    naval_fate.py ship new ...
     
    naval_fate.py ship  move   [--speed=]
     
    naval_fate.py ship shoot  
     
    naval_fate.py mine (set|remove)   [--moored | --drifting]
     
    naval_fate.py (-h | --help)
     
    naval_fate.py --version
     
    Options:
     
    -h --help     Show this screen.
     
    --version     Show version.
     
    --speed=  Speed in knots [default: 10].
     
    --moored      Moored (anchored) mine.
     
    --drifting    Drifting mine.
     
    """

    Comment se décortique l’usage des “[, (, {, | ” par exemple? Est ce qu’il y a une convention standard décrite quelque part qui concernerait toute l’informatique en général?

    Le deuxième sujet concerne le parsing des arguments en ligne de commande. Le module argparse de python semble dire qu’il suit la norme/le standard GNU/POSIX. les recherches sur le net dirigent sans cesse vers des librairies de parsing d’arguments de la ligne de commande pour des langages particuliers. Existe t il un standard langage agnostic qui décrit le parsing des arguments de la ligne de commande avec un document assez clair qui l’expliquerait? Le document (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html) semble aller dans ce sens, mais ne comprenant pas le sujet 1, j’ai du mal à décrypter ce document assez technique.

    Merci d’avance pour toute aide dans ma quête de connaissances

  • Sam Post author

    Y a un bon article a faire en effet. J’avais eu la même question quand j’ai débuté en informatique, et j’ai juste oublié que j’avais galéré là dessus.

    Pour le standard, je n’en connais pas. Ce sont des infos que j’ai glané à l’usage.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.