Arguments de ligne de commande Python – Real Python

By | février 5, 2020

Cours Python en ligne

Ajout de la capacité de traitement Arguments de ligne de commande Python fournit une interface conviviale à votre programme de ligne de commande basé sur du texte. Elle est similaire à ce qu'est une interface utilisateur graphique pour une application visuelle manipulée par des éléments graphiques ou des widgets.

Python expose un mécanisme pour capturer et extraire vos arguments de ligne de commande Python. Ces valeurs peuvent être utilisées pour modifier le comportement d'un programme. Par exemple, si votre programme traite les données lues dans un fichier, vous pouvez transmettre le nom du fichier à votre programme, plutôt que de coder en dur la valeur dans votre code source.

À la fin de ce didacticiel, vous saurez:

  • Les origines des arguments de ligne de commande Python
  • Le support sous-jacent pour les arguments de ligne de commande Python
  • Les normes guider la conception d'une interface de ligne de commande
  • Les bases personnaliser et gérer manuellement les arguments de ligne de commande Python
  • Les bibliothèques disponible en Python pour faciliter le développement d'une interface de ligne de commande complexe

Si vous voulez un moyen convivial de fournir des arguments de ligne de commande Python à votre programme sans importer une bibliothèque dédiée, ou si vous voulez mieux comprendre la base commune des bibliothèques existantes dédiées à la construction de l'interface de ligne de commande Python, alors gardez à la lecture!

L'interface de ligne de commande

UNE interface de ligne de commande (CLI) fournit un moyen pour un utilisateur d'interagir avec un programme exécuté dans un interpréteur de shell basé sur du texte. Quelques exemples d'interpréteurs shell sont Bash sous Linux ou invite de commande sous Windows. Une interface de ligne de commande est activée par l'interpréteur de shell qui expose une invite de commande. Il peut être caractérisé par les éléments suivants:

  • UNE commander ou programme
  • Zéro ou plusieurs lignes de commande arguments
  • Un production représentant le résultat de la commande
  • Documentation textuelle appelée usage ou Aidez-moi

Toutes les interfaces de ligne de commande ne peuvent pas fournir tous ces éléments, mais cette liste n'est pas non plus exhaustive. La complexité de la ligne de commande va de la possibilité de passer un seul argument à de nombreux arguments et options, un peu comme un langage spécifique au domaine. Par exemple, certains programmes peuvent lancer la documentation Web à partir de la ligne de commande ou démarrer un interpréteur de shell interactif comme Python.

Les deux exemples suivants avec la commande Python illustrent la description d'une interface de ligne de commande:

$ python -c "print ('Real Python')"
Vrai Python

Dans ce premier exemple, l'interpréteur Python prend l'option -c pour commander, qui indique d'exécuter les arguments de ligne de commande Python en suivant l'option -c en tant que programme Python.

Un autre exemple montre comment appeler Python avec -h pour afficher l'aide:

$ python -h
utilisation: python3 [option] ... [-c cmd | -m mod | file | -] [arg]    ...
Options et arguments (et variables d'environnement correspondantes):
-b: émet des avertissements concernant str (bytes_instance), str (bytearray_instance)
                                    et comparer les octets / bytearray avec str. (-bb: problèmes d'erreur)
[ ... complete help text not shown ... ]

Essayez ceci dans votre terminal pour voir la documentation d'aide complète.

L'héritage C

Les arguments de ligne de commande Python héritent directement du langage de programmation C. Comme Guido Van Rossum l'a écrit dans An Introduction to Python for Unix / C Programmers en 1993, C a eu une forte influence sur Python. Guido mentionne les définitions des littéraux, identificateurs, opérateurs et instructions comme Pause, continuer, ou revenir. L'utilisation d'arguments en ligne de commande Python est également fortement influencée par le langage C.

Pour illustrer les similitudes, considérons le programme C suivant:

    1 // principal c
    2 #comprendre 
    3 
    4 int principale(int argc, carboniser *argv[]) 
    5     printf("Nombre d'arguments:% d n", argc);
    6     pour (int je = 0; je < argc; je++) 
    sept         printf("Argument% 6d:% s n", je, argv[[[[je]);
    8     
    9     revenir 0;
dix 

La ligne 4 définit principale(), qui est le point d'entrée d'un programme C. Prenez bonne note des paramètres:

  1. argc est un entier représentant le nombre d'arguments du programme.
  2. argv est un tableau de pointeurs sur des caractères contenant le nom du programme dans le premier élément du tableau, suivi des arguments du programme, le cas échéant, dans les autres éléments du tableau.

Vous pouvez compiler le code ci-dessus sous Linux avec gcc -o main main.c, puis exécutez avec ./principale pour obtenir les éléments suivants:

$ gcc -o main main.c
$ ./principale
Nombre d'arguments: 1
Argument 0: ./main

Sauf si explicitement exprimé sur la ligne de commande avec l'option -o, a.out est le nom par défaut de l'exécutable généré par le gcc compilateur. Ça signifie sortie assembleur et rappelle les exécutables qui ont été générés sur les anciens systèmes UNIX. Observez que le nom de l'exécutable ./principale est le seul argument.

Pimentons cet exemple en passant quelques arguments de ligne de commande Python au même programme:

$ Arguments de ligne de commande Python ./main
Nombre d'arguments: 5
Argument 0: ./main
Argument 1: Python
Argument 2: commande
Argument 3: ligne
Argument 4: Arguments

La sortie montre que le nombre d'arguments est 5, et la liste des arguments inclut le nom du programme, principale, suivi de chaque mot de la phrase "Arguments de ligne de commande Python", que vous avez transmis sur la ligne de commande.

La compilation de principal c suppose que vous avez utilisé un système Linux ou Mac OS. Sous Windows, vous pouvez également compiler ce programme C avec l'une des options suivantes:

Si vous avez installé Microsoft Visual Studio ou les outils de build de Windows, vous pouvez compiler principal c comme suit:

Vous obtiendrez un exécutable nommé main.exe que vous pouvez commencer par:

C: />principale
Nombre d'arguments: 1
Argument 0: principal

Vous pouvez implémenter un programme Python, main.py, c'est l'équivalent du programme C, principal c, vous avez vu ci-dessus:

# main.py
importation sys

si __Nom__ == "__principale__":
    impression(F"Nombre d'arguments: len (sys.argv)")
    pour je, arg dans énumérer(sys.argv):
        impression(F"Argument i:> 6: arg")

Vous ne voyez pas de argc variable comme dans l'exemple de code C. Il n’existe pas en Python car sys.argv est suffisant. Vous pouvez analyser les arguments de ligne de commande Python dans sys.argv sans avoir à connaître la longueur de la liste, et vous pouvez appeler la fonction intégrée len () si le nombre d'arguments est requis par votre programme.

Notez également que énumérer(), lorsqu'il est appliqué à un itérable, renvoie un énumérer objet qui peut émettre des paires associant l'index d'un élément dans sys.arg à sa valeur correspondante. Cela permet de parcourir le contenu de sys.argv sans avoir à tenir un compteur pour l'index dans la liste.

Exécuter main.py comme suit:

$ python main.py Arguments de la ligne de commande Python
Nombre d'arguments: 5
Argument 0: main.py
Argument 1: Python
Argument 2: commande
Argument 3: ligne
Argument 4: Arguments

sys.argv contient les mêmes informations que dans le programme C:

  • Le nom du programme main.py est le premier élément de la liste.
  • Les arguments Python, Commander, Ligne, et Arguments sont les éléments restants de la liste.

Avec cette brève introduction à quelques aspects obscurs du langage C, vous êtes maintenant armé de connaissances précieuses pour mieux comprendre les arguments de la ligne de commande Python.

Deux utilitaires du monde Unix

Pour utiliser les arguments de ligne de commande Python dans ce didacticiel, vous allez implémenter certaines fonctionnalités partielles de deux utilitaires de l'écosystème Unix:

  1. sha1sum
  2. seq

Vous vous familiariserez avec ces outils Unix dans les sections suivantes.

sha1sum

sha1sum calcule les hachages SHA-1, et il est souvent utilisé pour vérifier l'intégrité des fichiers. Pour une entrée donnée, un fonction de hachage renvoie toujours la même valeur. Toute modification mineure de l'entrée entraînera une valeur de hachage différente. Avant d'utiliser l'utilitaire avec des paramètres concrets, vous pouvez essayer d'afficher l'aide:

$ sha1sum --help
Utilisation: sha1sum [OPTION]... [FILE]...
Imprimez ou vérifiez les sommes de contrôle SHA1 (160 bits).

Sans FICHIER, ou lorsque FICHIER est -, lisez l'entrée standard.

        -b, --binary lu en mode binaire
        -c, --check lire les sommes SHA1 des FICHIERS et les vérifier
                        --tag créer une somme de contrôle de style BSD
        -t, --text lu en mode texte (par défaut)
        -z, --zero termine chaque ligne de sortie par NUL, pas par une nouvelle ligne,
                                                                                            et désactiver l'échappement du nom de fichier
[ ... complete help text not shown ... ]

L'affichage de l'aide d'un programme en ligne de commande est une fonctionnalité courante exposée dans l'interface de ligne de commande.

Pour calculer la valeur de hachage SHA-1 du contenu d'un fichier, vous procédez comme suit:

$ sha1sum main.c
125a0f900ff6f164752600550879cbfabb098bc3 main.c

Le résultat affiche la valeur de hachage SHA-1 comme premier champ et le nom du fichier comme deuxième champ. La commande peut prendre plusieurs fichiers comme arguments:

$ sha1sum main.c main.py
125a0f900ff6f164752600550879cbfabb098bc3 main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4 main.py

Grâce à la fonctionnalité d'extension des caractères génériques du terminal Unix, il est également possible de fournir des arguments de ligne de commande Python avec des caractères génériques. Un tel personnage est l'astérisque ou l'étoile (*):

$ sha1sum main. *
3f6d5274d6317d580e2ffc1bf52beee0d94bf078 main.c
f41259ea5835446536d2e71e566075c1c1bfc111 main.py

La coquille convertit principale.* à principal c et main.py, qui sont les deux fichiers correspondant au modèle principale.* dans le répertoire courant et les transmet à sha1sum. Le programme calcule la Hachage SHA1 de chacun des fichiers de la liste des arguments. Vous verrez que, sous Windows, le comportement est différent. Windows n'a pas d'extension générique, le programme peut donc devoir s'adapter à cela. Votre implémentation peut avoir besoin d'étendre les caractères génériques en interne.

Sans aucun argument, sha1sum lit à partir de l'entrée standard. Vous pouvez envoyer des données au programme en tapant des caractères sur le clavier. L'entrée peut contenir n'importe quel caractère, y compris le retour chariot Entrer. Pour terminer l'entrée, vous devez signaler la fin du fichier avec Entrer, suivi de la séquence Ctrl+:

    1 $ sha1sum
 2 Réel
    3 Python
    4 87263a73c98af453d68ee4aab61576b331f8d9d6 -

Vous entrez d'abord le nom du programme, sha1sum, suivi par Entrer, puis Réel et Python, chacun également suivi de Entrer. Pour fermer le flux d'entrée, vous tapez Ctrl+. Le résultat est la valeur du hachage SHA1 généré pour le texte Réel nPython n. Le nom du fichier est -. Il s'agit d'une convention pour indiquer l'entrée standard. La valeur de hachage est la même lorsque vous exécutez les commandes suivantes:

$ python -c "print ('Real  nPython  n', end = '')" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6 -
$ python -c "print ('Real  nPython')" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6 -
$ printf "Réel  nPython  n" | sha1sum
87263a73c98af453d68ee4aab61576b331f8d9d6 -

Ensuite, vous lirez une brève description de seq.

seq

seq génère un séquence des nombres. Dans sa forme la plus élémentaire, comme la génération de la séquence de 1 à 5, vous pouvez exécuter ce qui suit:

Pour avoir un aperçu des possibilités exposées par seq, vous pouvez afficher l'aide sur la ligne de commande:

$ seq --help
Utilisation: seq [OPTION]... DERNIER
        ou: seq [OPTION]... PREMIER DERNIER
        ou: seq [OPTION]... PREMIÈRE AUGMENTATION DERNIÈRE
Imprimez les nombres du PREMIER au DERNIER, par étapes d'AUGMENTATION.

Les arguments obligatoires pour les options longues sont également obligatoires pour les options courtes.
        -f, --format = FORMAT utilise le format à virgule flottante du style printf
        -s, --separator = STRING utilise STRING pour séparer les nombres (par défaut:  n)
        -w, --equal-width égalise la largeur par un remplissage avec des zéros non significatifs
                        --help afficher cette aide et quitter
                        --version afficher les informations de version et quitter
[ ... complete help text not shown ... ]

Pour ce didacticiel, vous allez écrire quelques variantes simplifiées de sha1sum et seq. Dans chaque exemple, vous apprendrez une facette ou une combinaison de fonctionnalités différente concernant les arguments de ligne de commande Python.

Sous Mac OS et Linux, sha1sum et seq devrait être préinstallé, bien que les fonctionnalités et les informations d'aide puissent parfois différer légèrement entre les systèmes ou les distributions. Si vous utilisez Windows 10, la méthode la plus pratique consiste à exécuter sha1sum et seq dans un environnement Linux installé sur le WSL. Si vous n'avez pas accès à un terminal exposant les utilitaires Unix standard, vous pouvez avoir accès à des terminaux en ligne:

  • Créer un compte gratuit sur PythonAnywhere et démarrez une console Bash.
  • Créer un terminal Bash temporaire sur repl.it.

Ce sont deux exemples, et vous pouvez en trouver d'autres.

le sys.argv Array

Avant d'explorer certaines conventions acceptées et de découvrir comment gérer les arguments de ligne de commande Python, vous devez savoir que la prise en charge sous-jacente de tous les arguments de ligne de commande Python est fournie par sys.argv. Les exemples des sections suivantes vous montrent comment gérer les arguments de ligne de commande Python stockés dans sys.argv et pour surmonter les problèmes typiques qui se produisent lorsque vous essayez d'y accéder. Vous apprendrez:

  • Comment accès le contenu de sys.argv
  • Comment atténuer les effets secondaires de la nature mondiale de sys.argv
  • Comment processus espaces blancs dans les arguments de ligne de commande Python
  • Comment manipuler erreurs lors de l'accès aux arguments de ligne de commande Python
  • Comment ingérer le format d'origine des arguments de ligne de commande Python transmis par octets

Commençons!

Affichage des arguments

le sys module expose un tableau nommé argv qui comprend les éléments suivants:

  1. argv[0] contient le nom du programme Python actuel.
  2. argv[1:], le reste de la liste, contient tous les arguments de ligne de commande Python transmis au programme.

L'exemple suivant illustre le contenu de sys.argv:

    1 # argv.py
    2 importation sys
    3 
    4 impression(F"Nom du script: sys.argv[0]= ")
    5 impression(F"Arguments du script: sys.argv[1:]= ")

Voici comment ce code fonctionne:

  • Ligne 2 importe le module Python interne sys.
  • Ligne 4 extrait le nom du programme en accédant au premier élément de la liste sys.argv.
  • Ligne 5 affiche les arguments de la ligne de commande Python en récupérant tous les éléments restants de la liste sys.argv.

Exécutez le script argv.py ci-dessus avec une liste d'arguments arbitraires comme suit:

$ python argv.py un deux trois quatre
Nom du script: sys.argv[0]= 'argv.py'
Arguments du script: sys.argv[1:]=['un', 'deux', 'trois', 'quatre']

La sortie confirme que le contenu de sys.argv[0] est le script Python argv.py, et que les éléments restants de la sys.argv la liste contient les arguments du script, ['un', 'deux', 'trois', 'quatre'].

Résumer, sys.argv contient tous les argv.py Arguments de ligne de commande Python. Lorsque l'interpréteur Python exécute un programme Python, il analyse la ligne de commande et remplit sys.argv avec les arguments.

Inverser le premier argument

Maintenant que vous avez suffisamment de connaissances sur sys.argv, vous allez opérer sur les arguments passés sur la ligne de commande. L'exemple reverse.py inverse le premier argument passé sur la ligne de commande:

    1 # reverse.py
    2 
    3 importation sys
    4 
    5 arg = sys.argv[[[[1]
    6 impression(arg[::[::[::[::-1])

Dans reverse.py le processus pour inverser le premier argument est effectué avec les étapes suivantes:

  • Ligne 5 récupère le premier argument du programme stocké à l'index 1 de sys.argv. N'oubliez pas que le nom du programme est stocké dans l'index 0 de sys.argv.
  • Ligne 6 imprime la chaîne inversée. args[::-1] est une manière Pythonique d'utiliser une opération de tranche pour inverser une liste.

Vous exécutez le script comme suit:

$ python reverse.py "Real Python"
NohtyP laeR

Comme prévu, reverse.py fonctionne sur "Real Python" et inverse le seul argument à afficher "nohtyP laeR". Notez que l'entourage de la chaîne de plusieurs mots "Real Python" avec des guillemets garantit que l'interpréteur le traite comme un argument unique, au lieu de deux arguments. Vous plongerez dans séparateurs d'arguments dans une section ultérieure.

Muter sys.argv

sys.argv est disponible dans le monde à votre programme Python en cours d'exécution. Tous les modules importés lors de l'exécution du processus ont un accès direct à sys.argv. Cet accès global peut être pratique, mais sys.argv n'est pas immuable. Vous souhaiterez peut-être implémenter un mécanisme plus fiable pour exposer les arguments du programme à différents modules de votre programme Python, en particulier dans un programme complexe avec plusieurs fichiers.

Observez ce qui se passe si vous trafiquez sys.argv:

# argv_pop.py

importation sys

impression(sys.argv)
sys.argv.pop()
impression(sys.argv)

Vous invoquez .pop() pour supprimer et retourner le dernier élément sys.argv.

Exécutez le script ci-dessus:

$ python argv_pop.py un deux trois quatre
['argv_pop.py', 'un', 'deux', 'trois', 'quatre']
['argv_pop.py', 'un', 'deux', 'trois']

Notez que le quatrième argument n'est plus inclus dans sys.argv.

Dans un court script, vous pouvez compter en toute sécurité sur l'accès global à sys.argv, mais dans un programme plus grand, vous souhaiterez peut-être stocker les arguments dans une variable distincte. L'exemple précédent pourrait être modifié comme suit:

# argv_var_pop.py

importation sys

impression(sys.argv)
args = sys.argv[[[[1:]
impression(args)
sys.argv.pop()
impression(sys.argv)
impression(args)

Cette fois, bien que sys.argv a perdu son dernier élément, args a été préservé en toute sécurité. args n'est pas global, et vous pouvez le faire circuler pour analyser les arguments selon la logique de votre programme. Le gestionnaire de packages Python, pépin, utilise cette approche. Voici un court extrait du pépin code source:

def principale(args=Aucun):
    si args est Aucun:
        args = sys.argv[[[[1:]

Dans cet extrait de code extrait de la pépin code source, principale() enregistre dans args la tranche de sys.argv qui contient uniquement les arguments et non le nom du fichier. sys.argv reste intact, et args n'est pas affecté par des modifications involontaires sys.argv.

Échapper aux caractères d'espaces blancs

dans le reverse.py exemple que vous avez vu plus tôt, le premier et le seul argument est "Real Python", et le résultat est "nohtyP laeR". L'argument comprend un séparateur d'espaces entre "Réel" et "Python", et il doit être échappé.

Sous Linux, les espaces blancs peuvent être échappés en effectuant l'une des actions suivantes:

  1. Alentours les arguments avec des guillemets simples (")
  2. Alentours les arguments entre guillemets doubles (")
  3. Préfixe chaque espace avec une barre oblique inverse ()

Sans l'une des solutions d'échappement, reverse.py stocke deux arguments, "Réel" dans sys.argv[1] et "Python" dans sys.argv[2]:

$ python reverse.py Real Python
laeR

La sortie ci-dessus montre que le script inverse uniquement "Réel" et cela "Python" est ignoré. Pour vous assurer que les deux arguments sont stockés, vous devez entourer la chaîne globale de guillemets doubles (").

Vous pouvez également utiliser une barre oblique inverse () pour échapper au blanc:

$ python reverse.py Real Python
NohtyP laeR

Avec la barre oblique inverse (), le shell de commande expose un argument unique à Python, puis à reverse.py.

Dans les shells Unix, le séparateur de champ interne (IFS) définit les caractères utilisés comme délimiteurs. Le contenu de la variable shell, IFS, peut être affiché en exécutant la commande suivante:

$ printf "% q  n" "$ IFS"
$' t  n'

D'après le résultat ci-dessus, ' t n', vous identifiez trois délimiteurs:

  1. Espace ('')
  2. Languette ( t)
  3. Nouvelle ligne ( n)

Préfixer un espace avec une barre oblique inverse () contourne le comportement par défaut de l'espace comme délimiteur dans la chaîne "Real Python". Il en résulte un bloc de texte comme prévu, au lieu de deux.

Notez que, sous Windows, l'interprétation des espaces blancs peut être gérée en utilisant une combinaison de guillemets doubles. C'est un peu contre-intuitif car, dans le terminal Windows, une double citation (") est interprété comme un commutateur permettant de désactiver puis d'activer des caractères spéciaux tels que espace, languette, ou tuyau (|).

Par conséquent, lorsque vous entourez plusieurs chaînes de guillemets doubles, le terminal Windows interprète le premier guillemet double comme une commande pour ignorer les caractères spéciaux et la deuxième citation double comme l'un interpréter des caractères spéciaux.

Avec ces informations à l'esprit, il est sûr de supposer que le fait d'entourer plus d'une chaîne avec des guillemets doubles vous donnera le comportement attendu, qui est d'exposer le groupe de chaînes comme un seul argument. Pour confirmer cet effet particulier du guillemet double sur la ligne de commande Windows, observez les deux exemples suivants:

C: />python reverse.py "Real Python"
NohtyP laeR

Dans l'exemple ci-dessus, vous pouvez déduire intuitivement que "Real Python" est interprété comme un seul argument. Cependant, réalisez ce qui se produit lorsque vous utilisez une seule citation double:

C: />python reverse.py "Vrai Python
NohtyP laeR

L'invite de commande passe la chaîne entière "Real Python" comme un seul argument, de la même manière que si l'argument était "Real Python". En réalité, l'invite de commandes Windows voit le guillemet double unique comme un commutateur pour désactiver le comportement des espaces blancs en tant que séparateurs et transmet tout ce qui suit le guillemet double comme argument unique.

Pour plus d'informations sur les effets des guillemets doubles dans le terminal Windows, consultez Une meilleure façon de comprendre la citation et l'échappement des arguments de la ligne de commande Windows.

Gestion des erreurs

Les arguments de ligne de commande Python sont cordes lâches. Beaucoup de choses peuvent mal se passer, c'est donc une bonne idée de fournir aux utilisateurs de votre programme quelques conseils au cas où ils passeraient des arguments incorrects sur la ligne de commande. Par exemple, reverse.py attend un argument, et si vous l'omettez, vous obtenez une erreur:

    1 $ python reverse.py
 2 Traceback (dernier appel le plus récent):
    3         Fichier "reverse.py", ligne 5, dans 
    4                 arg = sys.argv[1]
    5 IndexError: liste des index hors limites

L'exception Python IndexError est déclenché et le retraçage correspondant montre que l'erreur est causée par l'expression arg = sys.argv[1]. Le message de l'exception est index de liste hors de portée. Vous n'avez pas passé d'argument sur la ligne de commande, il n'y a donc rien dans la liste sys.argv à l'index 1.

Il s'agit d'un modèle courant qui peut être traité de différentes manières. Pour cet exemple initial, vous serez bref en incluant l'expression arg = sys.argv[1] dans un essayer bloquer. Modifiez le code comme suit:

    1 # reverse_exc.py
    2 
    3 importation sys
    4 
    5 essayer:
    6     arg = sys.argv[[[[1]
    sept sauf IndexError:
    8     élever SystemExit(F"Usage: sys.argv[0] ")
    9 impression(arg[::[::[::[::-1])

L'expression de la ligne 4 est incluse dans un essayer bloquer. La ligne 8 lève l'exception intégrée SystemExit. Si aucun argument n'est transmis à reverse_exc.py, le processus se termine avec un code d'état de 1 après avoir imprimé l'utilisation. Notez l'intégration de sys.argv[0] dans le message d'erreur. Il expose le nom du programme dans le message d'utilisation. Maintenant, lorsque vous exécutez le même programme sans aucun argument de ligne de commande Python, vous pouvez voir la sortie suivante:

$ python reverse.py
Utilisation: reverse.py 

$ écho $?
1

reverse.py aucun argument n'a été transmis sur la ligne de commande. En conséquence, le programme soulève SystemExit avec un message d'erreur. Cela provoque la fermeture du programme avec un état de 1, qui s'affiche lorsque vous imprimez la variable spéciale $? avec écho.

Calcul de la sha1sum

Vous allez écrire un autre script pour démontrer que, sur les systèmes de type Unix, les arguments de ligne de commande Python sont transmis par octets à partir du système d'exploitation. Ce script prend une chaîne comme argument et génère le hachage hexadécimal SHA-1 de l'argument:

    1 # sha1sum.py
    2 
    3 importation sys
    4 importation hashlib
    5 
    6 Les données = sys.argv[[[[1]
    sept m = hashlib.sha1()
    8 m.mise à jour(octets(Les données, «utf-8»))
    9 impression(m.hexdigest())

Ceci est vaguement inspiré par sha1sum, mais il traite intentionnellement une chaîne au lieu du contenu d'un fichier. Dans sha1sum.py, les étapes pour ingérer les arguments de ligne de commande Python et pour générer le résultat sont les suivantes:

  • Ligne 6 stocke le contenu du premier argument dans Les données.
  • Ligne 7 instancie un algorithme SHA1.
  • Ligne 8 met à jour l'objet de hachage SHA1 avec le contenu du premier argument de programme. Notez que hash.update prend un tableau d'octets comme argument, il est donc nécessaire de convertir Les données d'une chaîne à un tableau d'octets.
  • Ligne 9 imprime une représentation hexadécimale du hachage SHA1 calculé sur la ligne 8.

Lorsque vous exécutez le script avec un argument, vous obtenez ceci:

$ python sha1sum.py "Real Python"
0554943d034f044c5998f55dac8ee2c03e387565

Pour garder l'exemple court, le script sha1sum.py ne gère pas les arguments de ligne de commande Python manquants. La gestion des erreurs peut être traitée dans ce script de la même manière que dans reverse_exc.py.

Du sys.argv la documentation, vous apprenez que pour obtenir les octets d'origine des arguments de ligne de commande Python, vous pouvez utiliser os.fsencode (). En obtenant directement les octets de sys.argv[1], vous n'avez pas besoin d'effectuer la conversion de chaîne en octets de Les données:

    1 # sha1sum_bytes.py
    2 
    3 importation os
    4 importation sys
    5 importation hashlib
    6 
    sept Les données = os.fsencode(sys.argv[[[[1])
    8 m = hashlib.sha1()
    9 m.mise à jour(Les données)
dix impression(m.hexdigest())

La principale différence entre sha1sum.py et sha1sum_bytes.py sont mis en évidence dans les lignes suivantes:

  • Ligne 7 remplit Les données avec les octets d'origine passés aux arguments de ligne de commande Python.
  • Ligne 9 passe Les données comme argument pour m.update (), qui reçoit un objet de type octets.

Exécuter sha1sum_bytes.py pour comparer la sortie:

$ python sha1sum_bytes.py "Real Python"
0554943d034f044c5998f55dac8ee2c03e387565

La valeur hexadécimale du hachage SHA1 est la même que dans la précédente sha1sum.py exemple.

Arguments de la ligne de commande de l'anatomie de Python

Maintenant que vous avez exploré quelques aspects des arguments de ligne de commande Python, notamment sys.argv, vous allez appliquer certaines des normes qui sont régulièrement utilisées par les développeurs lors de l'implémentation d'une interface de ligne de commande.

Les arguments de ligne de commande Python sont un sous-ensemble de l'interface de ligne de commande. Ils peuvent être composés de différents types d'arguments:

  1. Les options modifier le comportement d'une commande ou d'un programme particulier.
  2. Arguments représentent la source ou la destination à traiter.
  3. Sous-commandes permettre à un programme de définir plus d'une commande avec l'ensemble respectif d'options et d'arguments.

Avant d'approfondir les différents types d'arguments, vous obtiendrez un aperçu des normes acceptées qui ont guidé la conception de l'interface de ligne de commande et des arguments. Celles-ci ont été affinées depuis l'avènement du terminal informatique au milieu des années 60.

Normes

Quelques disponibles normes fournir quelques définitions et directives pour promouvoir la cohérence de l'implémentation des commandes et de leurs arguments. Ce sont les principales normes et références UNIX:

Les normes ci-dessus définissent des directives et des nomenclatures pour tout ce qui concerne les programmes et les arguments de ligne de commande Python. Les points suivants sont des exemples tirés de ces références:

  • POSIX:
    • Un programme ou un utilitaire est suivi d'options, d'arguments d'option et d'opérandes.
    • Toutes les options doivent être précédées d'un trait d'union ou d'un signe moins (-) caractère délimiteur.
    • Les arguments d'option ne doivent pas être facultatifs.
  • GNOU:
    • Tous les programmes doivent prendre en charge deux options standard, qui sont --version et --Aidez-moi.
    • Les options portant un nom long sont équivalentes aux options de style Unix à une seule lettre. Un exemple est --déboguer et -ré.
  • docopt:
    • Les options courtes peuvent être empilées, ce qui signifie que -abc est équivalent à -a -b -c.
    • Les options longues peuvent avoir des arguments spécifiés après un espace ou le signe égal (=). L'option longue --input = ARG est équivalent à - entrée ARG.

Ces normes définissent des notations utiles lorsque vous décrivez une commande. Une notation similaire peut être utilisée pour afficher l'utilisation d'une commande particulière lorsque vous l'invoquez avec l'option -h ou --Aidez-moi.

Les normes GNU sont très similaires aux normes POSIX mais fournissent quelques modifications et extensions. Ils ajoutent notamment option longue c'est une option entièrement nommée, précédée de deux tirets (-). Par exemple, pour afficher l'aide, l'option régulière est -h et l'option longue est --Aidez-moi.

Dans les sections suivantes, vous en apprendrez plus sur chacun des composants, options, arguments et sous-commandes de la ligne de commande.

Les options

Un option, parfois appelé drapeau ou un commutateur, est destiné à modifier le comportement du programme. Par exemple, la commande ls sous Linux répertorie le contenu d'un répertoire donné. Sans aucun argument, il répertorie les fichiers et répertoires du répertoire courant:

$ CD / dev
$ ls
autofs
bloquer
bsg
btrfs-control
autobus
carboniser
console

Ajoutons quelques options. Vous pouvez combiner -l et -s dans -ls, ce qui modifie les informations affichées dans le terminal:

$ CD / dev
$ ls -ls
total 0
0 crw-r - r-- 1 racine root 10, 235 juil 14 08:10 autofs
0 drwxr-xr-x 2 root root 260 juil 14 bloc 08:10
0 drwxr-xr-x 2 root root 60 juil.14 08:10 bsg
0 crw ------- 1 racine root 10, 234 juillet 14 08:10 btrfs-control
0 drwxr-xr-x 3 root root 60 juil 14 08:10 bus
0 drwxr-xr-x 2 root root 4380 14 juillet 15:08 car
0 crw ------- 1 root root 5, 1 juil 14 08:10 console

Un option peut prendre un argument, appelé option-argument. Voir un exemple en action avec od au dessous de:

$ od -t x1z -N 16 principale
0000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00> .ELF ............ <
0000020

od signifie vidage octal. Cet utilitaire affiche les données dans différentes représentations imprimables, comme octal (qui est la valeur par défaut), hexadécimal, décimal et ASCII. Dans l'exemple ci-dessus, il prend le fichier binaire principale et affiche les 16 premiers octets du fichier au format hexadécimal. L'option -t attend un type comme argument-option, et -N attend le nombre d'octets d'entrée.

Dans l'exemple ci-dessus, -t est donné un type x1, qui signifie hexadécimal et un octet par entier. Ceci est suivi par z pour afficher les caractères imprimables à la fin de la ligne de saisie. -N prend 16 comme argument d'option pour limiter le nombre d'octets d'entrée à 16.

Arguments

le arguments sont également appelés opérandes ou des paramètres dans les normes POSIX. Les arguments représentent la source ou la destination des données sur lesquelles la commande agit. Par exemple, la commande cp, qui est utilisé pour copier un ou plusieurs fichiers dans un fichier ou un répertoire, prend au moins une source et une cible:

    1 $ ls main
 2 principale
    3 
    4 $ cp main main2
    5 
    6 $ ls -lt
 sept principale
    8 main2
    9 ...

À la ligne 4, cp prend deux arguments:

  1. principale: le fichier source
  2. main2: le fichier cible

Il copie ensuite le contenu de principale dans un nouveau fichier nommé main2. Tous les deux principale et main2 sont des arguments ou des opérandes du programme cp.

Sous-commandes

Le concept de sous-commandes n'est pas documenté dans les normes POSIX ou GNU, mais il apparaît dans docopt. Les utilitaires Unix standard sont de petits outils adhérant à la philosophie Unix. Les programmes Unix sont destinés à être des programmes qui font une chose et le font bien. Cela signifie qu'aucune sous-commande n'est nécessaire.

En revanche, une nouvelle génération de programmes, comprenant git, aller, docker, et gcloud, viennent avec un paradigme légèrement différent qui englobe les sous-commandes. Ils ne font pas nécessairement partie du paysage Unix car ils couvrent plusieurs systèmes d'exploitation, et ils sont déployés avec un écosystème complet qui nécessite plusieurs commandes.

Prendre git par exemple. Il gère plusieurs commandes, chacune possiblement avec son propre ensemble d'options, d'options-arguments et d'arguments. Les exemples suivants s'appliquent à la sous-commande git branche:

  • git branch affiche les branches du référentiel git local.
  • git branch custom_python crée une succursale locale custom_python dans un référentiel local.
  • git branch -d custom_python supprime la branche locale custom_python.
  • git branch --help affiche l'aide pour le git branch sous-commande.

Dans l'écosystème Python, pépin a aussi le concept de sous-commandes. Certains pépin les sous-commandes incluent liste, installer, Geler, ou désinstaller.

les fenêtres

Sous Windows, les conventions concernant les arguments de ligne de commande Python sont légèrement différentes, en particulier celles concernant les options de ligne de commande. Pour valider cette différence, prenez liste de tâches, qui est un exécutable Windows natif qui affiche une liste des processus en cours d'exécution. C'est similaire à ps sur les systèmes Linux ou macOS. Vous trouverez ci-dessous un exemple d'exécution liste de tâches dans une invite de commande sous Windows:

C: />liste des tâches / FI "IMAGENAME eq notepad.exe"

Nom de l'image Nom de session PID Session # Utilisation Mem
========================= ======== ================= = ========== ============
notepad.exe 13104 Console 6 13 548 K
notepad.exe 6584 Console 6 13 696 K

Notez que le séparateur d'une option est une barre oblique (/) au lieu d'un trait d'union (-) comme les conventions pour les systèmes Unix. Pour plus de lisibilité, il y a un espace entre le nom du programme, liste des tâcheset l'option /FI, mais il est tout aussi correct de taper liste des tâches / FI.

L'exemple particulier ci-dessus s'exécute liste de tâches avec un filtre pour afficher uniquement les processus du Bloc-notes en cours d'exécution. Vous pouvez voir que le système a deux instances en cours d'exécution du processus Bloc-notes. Bien que ce ne soit pas équivalent, cela revient à exécuter la commande suivante dans un terminal sur un système de type Unix:

$ ps -ef | grep vi | grep -v grep
andre 2117 4 0 13:33 tty1 00:00:00 vi .gitignore
andre 2163 2134 0 13:34 tty3 00:00:00 vi main.c

le ps la commande ci-dessus montre tout le fonctionnement en cours vi processus. Le comportement est conforme à la philosophie Unix, car la sortie de ps est transformé par deux grep filtres. La première grep commande sélectionne toutes les occurrences de vi, et le deuxième grep filtre l'occurrence de grep lui-même.

With the spread of Unix tools making their appearance in the Windows ecosystem, non-Windows-specific conventions are also accepted on Windows.

Visuals

At the start of a Python process, Python command line arguments are split into two categories:

  1. Python options: These influence the execution of the Python interpreter. For example, adding option -O is a means to optimize the execution of a Python program by removing assert et __debug__ statements. There are other Python options available at the command line.

  2. Python program and its arguments: Following the Python options (if there are any), you’ll find the Python program, which is a file name that usually has the extension .py, and its arguments. By convention, those can also be composed of options and arguments.

Take the following command that’s intended to execute the program main.py, which takes options and arguments. Note that, in this example, the Python interpreter also takes some options, which are -B et -v.

$ python -B -v main.py --verbose --debug un deux

In the command line above, the options are Python command line arguments and are organized as follows:

  • The option -B tells Python not to write .pyc files on the import of source modules. For more details about .pyc files, check out the section What Does a Compiler Do? in Your Guide to the CPython Source Code.
  • The option -v stands for verbose and tells Python to trace all import statements.
  • The arguments passed to main.py are fictitious and represent two long options (--verbose et --debug) and two arguments (ONU et deux).

This example of Python command line arguments can be illustrated graphically as follows:

Anatomy of the Python Command Line Arguments

Within the Python program main.py, you only have access to the Python command line arguments inserted by Python in sys.argv. The Python options may influence the behavior of the program but are not accessible in main.py.

A Few Methods for Parsing Python Command Line Arguments

Now you’re going to explore a few approaches to apprehend options, option-arguments, and operands. This is done by parsing Python command line arguments. In this section, you’ll see some concrete aspects of Python command line arguments and techniques to handle them. First, you’ll see an example that introduces a straight approach relying on list comprehensions to collect and separate options from arguments. Then you will:

  • Utilisation regular expressions to extract elements of the command line
  • Apprendre how to handle files passed at the command line
  • Apprehend the standard input in a way that’s compatible with the Unix tools
  • Differentiate the regular output of the program from the errors
  • Implement a custom parser to read Python command line arguments

This will serve as a preparation for options involving modules in the standard libraries or from external libraries that you’ll learn about later in this tutorial.

For something uncomplicated, the following pattern, which doesn’t enforce ordering and doesn’t handle option-arguments, may be enough:

# cul.py

importation sys

opts = [[[[opt pour opt dans sys.argv[[[[1:] si opt.startswith("-")]
args = [[[[arg pour arg dans sys.argv[[[[1:] si ne pas arg.startswith("-")]

si "-c" dans opts:
    impression(" ".join(arg.capitalize() pour arg dans args))
elif "-u" dans opts:
    impression(" ".join(arg.upper() pour arg dans args))
elif "-l" dans opts:
    impression(" ".join(arg.inférieur() pour arg dans args))
autre:
    raise SystemExit(F"Usage: sys.argv[0]    (-c | -u | -l) ...")

The intent of the program above is to modify the case of the Python command line arguments. Three options are available:

  • -c to capitalize the arguments
  • -u to convert the arguments to uppercase
  • -l to convert the argument to lowercase

The code collects and separates the different argument types using list comprehensions:

  • Line 5 collects all the les options by filtering on any Python command line arguments starting with a hyphen (-).
  • Line 6 assembles the program arguments by filtering out the options.

When you execute the Python program above with a set of options and arguments, you get the following output:

$ python cul.py -c un deux trois
Un Deux Trois

This approach might suffice in many situations, but it would fail in the following cases:

  • If the order is important, and in particular, if options should appear before the arguments
  • If support for option-arguments is needed
  • If some arguments are prefixed with a hyphen (-)

You can leverage other options before you resort to a library like argparse ou click.

Regular Expressions

You can use a regular expression to enforce a certain order, specific options and option-arguments, or even the type of arguments. To illustrate the usage of a regular expression to parse Python command line arguments, you’ll implement a Python version of seq, which is a program that prints a sequence of numbers. Following the docopt conventions, a specification for seq.py could be this:

Print integers from  à , in steps of .

Usage:
  python seq.py --help
  python seq.py [-s SEPARATOR] 
  
  
  
  python seq.py [-s SEPARATOR]  
  
  
  
  python seq.py [-s SEPARATOR]   







Mandatory arguments to long options are mandatory for short options too.
  -s, --separator=STRING use STRING to separate numbers (default: n)
      --help             display this help and exit

Si  ou  are omitted, they default to 1. When  est
larger than , , if not set, defaults to -1.
The sequence of numbers ends when the sum of the current number and
 reaches the limit imposed by .

First, look at a regular expression that’s intended to capture the requirements above:

    1 args_pattern = re.compile(
    2     r"""
    3                 ^
    4                 (
    5                                 (--(?Phelp).*)|
    6                                 ((?:-s|--separator)s(?P.*?)s)?
    sept                                 ((?P-?d+))(s(?P-?d+))?(s(?P-?d+))?
    8                 )
    9                 $
dix """,
11     re.VERBOSE,
12 )

To experiment with the regular expression above, you may use the snippet recorded on Regular Expression 101. The regular expression captures and enforces a few aspects of the requirements given for seq. In particular, the command may take:

  1. A help option, in short (-h) or long format (--help), captured as a named group called HELP
  2. A separator option, -s ou --separator, taking an optional argument, and captured as named group called SEP
  3. Up to three integer operands, respectively captured as OP1, OP2, et OP3

For clarity, the pattern args_pattern above uses the flag re.VERBOSE on line 11. This allows you to spread the regular expression over a few lines to enhance readability. The pattern validates the following:

  • Argument order: Options and arguments are expected to be laid out in a given order. For example, options are expected before the arguments.
  • Option values**: Only --help, -s, ou --separator are expected as options.
  • Argument mutual exclusivity: The option --help isn’t compatible with other options or arguments.
  • Argument type: Operands are expected to be positive or negative integers.

For the regular expression to be able to handle these things, it needs to see all Python command line arguments in one string. You can collect them using str.join():

arg_line = " ".join(sys.argv[[[[1:])

Cela fait arg_line a string that includes all arguments, except the program name, separated by a space.

Given the pattern args_pattern above, you can extract the Python command line arguments with the following function:

def parse(arg_line: str) -> Dict[[[[str, str]:
    args: Dict[[[[str, str] = 
    si match_object := args_pattern.match(arg_line):
        args = k: v pour k, v dans match_object.groupdict().articles()
                si v est ne pas Aucun
    return args

The pattern is already handling the order of the arguments, mutual exclusivity between options and arguments, and the type of the arguments. parse() is applying re.match() to the argument line to extract the proper values and store the data in a dictionary.

The dictionary includes the names of each group as keys and their respective values. For example, if the arg_line value is --help, then the dictionary is 'HELP': 'help'. Si arg_line est -s T 10, then the dictionary becomes 'SEP': 'T', 'OP1': '10'. You can expand the code block below to see an implementation of seq with regular expressions.

The code below implements a limited version of seq with a regular expression to handle the command line parsing and validation:

# seq_regex.py

de typing importation liste, Dict
importation re
importation sys

USAGE = (
    F"Usage: sys.argv[0]    [-s[-s[-s[-s] [first [increment]]last"
)

args_pattern = re.compile(
    r"""
                ^
                (
                                (--(?Phelp).*)|
                                ((?:-s|--separator)s(?P.*?)s)?
                                ((?P-?d+))(s(?P-?d+))?(s(?P-?d+))?
                )
                $
""",
    re.VERBOSE,
)

def parse(arg_line: str) -> Dict[[[[str, str]:
    args: Dict[[[[str, str] = 
    si match_object := args_pattern.match(arg_line):
        args = k: v pour k, v dans match_object.groupdict().articles()
                si v est ne pas Aucun
    return args

def seq(operands: liste[[[[int], sep: str = "n") -> str:
    first, increment, last = 1, 1, 1
    si len(operands) == 1:
        last = operands[[[[0]
    si len(operands) == 2:
        first, last = operands
        si first > last:
            increment = -1
    si len(operands) == 3:
        first, increment, last = operands
    last = last + 1 si increment > 0 autre last - 1
    return sep.join(str(je) pour je dans range(first, last, increment))

def main() -> Aucun:
    args = parse(" ".join(sys.argv[[[[1:]))
    si ne pas args:
        raise SystemExit(USAGE)
    si args.avoir("HELP"):
        impression(USAGE)
        return
    operands = [[[[int(v) pour k, v dans args.articles() si k.startswith("OP")]
    sep = args.avoir("SEP", "n")
    impression(seq(operands, sep))

si __name__ == "__main__":
    main()

You can execute the code above by running this command:

This should output the following:

Try this command with other combinations, including the --help option.

You didn’t see a version option supplied here. This was done intentionally to reduce the length of the example. You may consider adding the version option as an extended exercise. As a hint, you could modify the regular expression by replacing the line (--(?Phelp).*)| with (--(?Phelp).*)|(--(?Pversion).*)|. Un montant supplémentaire de si block would also be needed in main().

At this point, you know a few ways to extract options and arguments from the command line. So far, the Python command line arguments were only strings or integers. Next, you’ll learn how to handle files passed as arguments.

File Handling

It’s time now to experiment with Python command line arguments that are expected to be file names. Modify sha1sum.py to handle one or more files as arguments. You’ll end up with a downgraded version of the original sha1sum utility, which takes one or more files as arguments and displays the hexadecimal SHA1 hash for each file, followed by the name of the file:

# sha1sum_file.py

importation hashlib
importation sys

def sha1sum(filename: str) -> str:
    hash = hashlib.sha1()
    with open(filename, mode="rb") as F:
        hash.mise à jour(F.lis())
    return hash.hexdigest()

pour arg dans sys.argv[[[[1:]:
    impression(F"sha1sum(arg)  arg")

sha1sum() is applied to the data read from each file that you passed at the command line, rather than the string itself. Take note that m.update() takes a bytes-like object as an argument and that the result of invoking read() after opening a file with the mode rb will return a bytes object. For more information about handling file content, check out Reading and Writing Files in Python, and in particular, the section Working With Bytes.

The evolution of sha1sum_file.py from handling strings at the command line to manipulating the content of files is getting you closer to the original implementation of sha1sum:

$ sha1sum main main.c
9a6f82c245f5980082dbf6faac47e5085083c07d  main
125a0f900ff6f164752600550879cbfabb098bc3  main.c

The execution of the Python program with the same Python command line arguments gives this:

$ python sha1sum_file.py main main.c
9a6f82c245f5980082dbf6faac47e5085083c07d  main
125a0f900ff6f164752600550879cbfabb098bc3  main.c

Because you interact with the shell interpreter or the Windows command prompt, you also get the benefit of the wildcard expansion provided by the shell. To prove this, you can reuse main.py, which displays each argument with the argument number and its value:

$ python main.py main.*
Arguments count: 5
Argument      0: main.py
Argument      1: main.c
Argument      2: main.exe
Argument      3: main.obj
Argument      4: main.py

You can see that the shell automatically performs wildcard expansion so that any file with a base name matching main, regardless of the extension, is part of sys.argv.

The wildcard expansion isn’t available on Windows. To obtain the same behavior, you need to implement it in your code. To refactor main.py to work with wildcard expansion, you can use glob. The following example works on Windows and, though it isn’t as concise as the original main.py, the same code behaves similarly across platforms:

    1 # main_win.py
    2 
    3 importation sys
    4 importation glob
    5 importation itertools
    6 de typing importation liste
    sept 
    8 def expand_args(args: liste[[[[str]) -> liste[[[[str]:
    9     arguments = args[:[:[:[:1]
dix     glob_args = [[[[glob.glob(arg) pour arg dans args[[[[1:]]
11     arguments += itertools.chain.from_iterable(glob_args)
12     return arguments
13 
14 si __name__ == "__main__":
15     args = expand_args(sys.argv)
16     impression(F"Arguments count: len(args)")
17     pour je, arg dans enumerate(args):
18         impression(F"Argument i:>6: arg")

Dans main_win.py, expand_args relies on glob.glob() to process the shell-style wildcards. You can verify the result on Windows and any other operating system:

C:/>python main_win.py main.*
Arguments count: 5
Argument      0: main_win.py
Argument      1: main.c
Argument      2: main.exe
Argument      3: main.obj
Argument      4: main.py

This addresses the problem of handling files using wildcards like the asterisk (*) or question mark (?), but how about stdin?

If you don’t pass any parameter to the original sha1sum utility, then it expects to read data from the standard input. This is the text you enter at the terminal that ends when you type Ctrl+ on Unix-like systems or Ctrl+Z on Windows. These control sequences send an end of file (EOF) to the terminal, which stops reading from stdin and returns the data that was entered.

In the next section, you’ll add to your code the ability to read from the standard input stream.

Standard Input

When you modify the previous Python implementation of sha1sum to handle the standard input using sys.stdin, you’ll get closer to the original sha1sum:

# sha1sum_stdin.py

de typing importation liste
importation hashlib
importation pathlib
importation sys

def process_file(filename: str) -> bytes:
    return pathlib.Chemin(filename).read_bytes()

def process_stdin() -> bytes:
    return bytes("".join(sys.stdin), "utf-8")

def sha1sum(Les données: bytes) -> str:
    sha1_hash = hashlib.sha1()
    sha1_hash.mise à jour(Les données)
    return sha1_hash.hexdigest()

def output_sha1sum(Les données: bytes, filename: str = "-") -> Aucun:
    impression(F"sha1sum(data)  filename")

def main(args: liste[[[[str]) -> Aucun:
    si ne pas args:
        args = [[[["-"]
    pour arg dans args:
        si arg == "-":
            output_sha1sum(process_stdin(), "-")
        autre:
            output_sha1sum(process_file(arg), arg)

si __name__ == "__main__":
    main(sys.argv[[[[1:])

Two conventions are applied to this new sha1sum version:

  1. Without any arguments, the program expects the data to be provided in the standard input, sys.stdin, which is a readable file object.
  2. When a hyphen (-) is provided as a file argument at the command line, the program interprets it as reading the file from the standard input.

Try this new script without any arguments. Enter the first aphorism of The Zen of Python, then complete the entry with the keyboard shortcut Ctrl+ on Unix-like systems or Ctrl+Z on Windows:

$ python sha1sum_stdin.py
Beau, c'est mieux que laid.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4  -

You can also include one of the arguments as stdin mixed with the other file arguments like so:

$ python sha1sum_stdin.py main.py - main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4  main.py
Beau, c'est mieux que laid.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4  -
125a0f900ff6f164752600550879cbfabb098bc3  main.c

Another approach on Unix-like systems is to provide /dev/stdin au lieu de - to handle the standard input:

$ python sha1sum_stdin.py main.py /dev/stdin main.c
d84372fc77a90336b6bb7c5e959bcb1b24c608b4  main.py
Beau, c'est mieux que laid.
ae5705a3efd4488dfc2b4b80df85f60c67d998c4  /dev/stdin
125a0f900ff6f164752600550879cbfabb098bc3  main.c

On Windows there’s no equivalent to /dev/stdin, so using - as a file argument works as expected.

The script sha1sum_stdin.py isn’t covering all necessary error handling, but you’ll cover some of the missing features later in this tutorial.

Standard Output and Standard Error

Command line processing may have a direct relationship with stdin to respect the conventions detailed in the previous section. The standard output, although not immediately relevant, is still a concern if you want to adhere to the Unix Philosophy. To allow small programs to be combined, you may have to take into account the three standard streams:

  1. stdin
  2. stdout
  3. stderr

The output of a program becomes the input of another one, allowing you to chain small utilities. For example, if you wanted to sort the aphorisms of the Zen of Python, then you could execute the following:

$ python -c "import this" | sort
Bien que jamais n'est souvent mieux que * en ce moment *.
Bien que la praticité l'emporte sur la pureté.
Bien que cela ne soit pas évident au début, sauf si vous êtes néerlandais.
...

The output above is truncated for better readability. Now imagine that you have a program that outputs the same data but also prints some debugging information:

# zen_sort_debug.py

impression("DEBUG >>> About to print the Zen of Python")
importation cette
impression("DEBUG >>> Done printing the Zen of Python")

Executing the Python script above gives:

$ python zen_sort_debug.py
DEBUG >>> About to print the Zen of Python
Le Zen de Python, par Tim Peters

Beau, c'est mieux que laid.
Explicite vaut mieux qu'implicite.
Simple, c'est mieux que complexe.
Complexe vaut mieux que compliqué.
...
DEBUG >>> Done printing the Zen of Python

The ellipsis (...) indicates that the output was truncated to improve readability.

Now, if you want to sort the list of aphorisms, then execute the command as follows:

$ python zen_sort_debug.py | sort

Bien que jamais n'est souvent mieux que * en ce moment *.
Bien que la praticité l'emporte sur la pureté.
Bien que cela ne soit pas évident au début, sauf si vous êtes néerlandais.
Beau, c'est mieux que laid.
Complexe vaut mieux que compliqué.
DEBUG >>> About to print the Zen of Python
DEBUG >>> Done printing the Zen of Python
Les erreurs ne doivent jamais passer silencieusement.
...

You may realize that you didn’t intend to have the debug output as the input of the sort command. To address this issue, you want to send traces to the standard errors stream, stderr, instead:

# zen_sort_stderr.py
importation sys

impression("DEBUG >>> About to print the Zen of Python", fichier=sys.stderr)
importation cette
impression("DEBUG >>> Done printing the Zen of Python", fichier=sys.stderr)

Execute zen_sort_stderr.py to observe the following:

$ python zen_sort_stderr.py | sort
DEBUG >>> About to print the Zen of Python
DEBUG >>> Done printing the Zen of Python

Bien que jamais n'est souvent mieux que * en ce moment *.
Bien que la praticité l'emporte sur la pureté.
Although that way may not be obvious at first unless you're Dutch
....

Now, the traces are displayed to the terminal, but they aren’t used as input for the sort command.

Custom Parsers

You can implement seq by relying on a regular expression if the arguments aren’t too complex. Nevertheless, the regex pattern may quickly render the maintenance of the script difficult. Before you try getting help from specific libraries, another approach is to create a custom parser. The parser is a loop that fetches each argument one after another and applies a custom logic based on the semantics of your program.

A possible implementation for processing the arguments of seq_parse.py could be as follows:

    1 def parse(args: liste[[[[str]) -> Tuple[[[[str, liste[[[[int]]:
    2     arguments = collections.deque(args)
    3     separator = "n"
    4     operands: liste[[[[int] = []
    5     while arguments:
    6         arg = arguments.popleft()
    sept         si ne pas operands:
    8             si arg == "--help":
    9                 impression(USAGE)
dix                 sys.exit(0)
11             si arg dans ("-s", "--separator"):
12                 separator = arguments.popleft()
13                 continuer
14         try:
15             operands.append(int(arg))
16         except ValueError:
17             raise SystemExit(USAGE)
18         si len(operands) > 3:
19             raise SystemExit(USAGE)
20 
21     return separator, operands

parse() is given the list of arguments without the Python file name and uses collections.deque() to get the benefit of .popleft(), which removes the elements from the left of the collection. As the items of the arguments list unfold, you apply the logic that’s expected for your program. Dans parse() you can observe the following:

  • le while loop is at the core of the function, and terminates when there are no more arguments to parse, when the help is invoked, or when an error occurs.
  • Si la separator option is detected, then the next argument is expected to be the separator.
  • operands stores the integers that are used to calculate the sequence. There should be at least one operand and at most three.

A full version of the code for parse() is available below:

# seq_parse.py

de typing importation Dict, liste, Tuple
importation collections
importation re
importation sys

USAGE = (F"Usage: sys.argv[0]    "
         "[--help] |[-s[-s[-s[-s] [first [incr]]last")

def seq(operands: liste[[[[int], sep: str = "n") -> str:
    first, increment, last = 1, 1, 1
    si len(operands) == 1:
        last = operands[[[[0]
    si len(operands) == 2:
        first, last = operands
        si first > last:
            increment = -1
    si len(operands) == 3:
        first, increment, last = operands
    last = last + 1 si increment > 0 autre last - 1
    return sep.join(str(je) pour je dans range(first, last, increment))

def parse(args: liste[[[[str]) -> Tuple[[[[str, liste[[[[int]]:
    arguments = collections.deque(args)
    separator = "n"
    operands: liste[[[[int] = []
    while arguments:
        arg = arguments.popleft()
        si ne pas len(operands):
            si arg == "--help":
                impression(USAGE)
                sys.exit(0)
            si arg dans ("-s", "--separator"):
                separator = arguments.popleft() si arguments autre Aucun
                continuer
        try:
            operands.append(int(arg))
        except ValueError:
            raise SystemExit(USAGE)
        si len(operands) > 3:
            raise SystemExit(USAGE)

    return separator, operands

def main() -> Aucun:
    sep, operands = parse(sys.argv[[[[1:])
    si ne pas operands:
        raise SystemExit(USAGE)
    impression(seq(operands, sep))

si __name__ == "__main__":
    main()

Note that some error handling aspects are kept to a minimum so as to keep the examples relatively short.

This manual approach of parsing the Python command line arguments may be sufficient for a simple set of arguments. However, it becomes quickly error-prone when complexity increases due to the following:

  • A large number of arguments
  • Complexity and interdependency between arguments
  • Validation to perform against the arguments

The custom approach isn’t reusable and requires reinventing the wheel in each program. By the end of this tutorial, you’ll have improved on this hand-crafted solution and learned a few better methods.

A Few Methods for Validating Python Command Line Arguments

You’ve already performed validation for Python command line arguments in a few examples like seq_regex.py et seq_parse.py. In the first example, you used a regular expression, and in the second example, a custom parser.

Both of these examples took the same aspects into account. They considered the expected les options as short-form (-s) or long-form (--separator). They considered the order of the arguments so that options would not be placed after operands. Finally, they considered the type, integer for the operands, and the number of arguments, from one to three arguments.

Type Validation With Python Data Classes

The following is a proof of concept that attempts to validate the type of the arguments passed at the command line. In the following example, you validate the number of arguments and their respective type:

# val_type_dc.py

importation dataclasses
importation sys
de typing importation liste, Any

USAGE = F"Usage: python sys.argv[0] [--help]    | firstname lastname age]"

@dataclasses.dataclass
class Arguments:
    firstname: str
    lastname: str
    age: int = 0

def check_type(obj):
    pour field dans dataclasses.fields(obj):
        value = getattr(obj, field.name)
        impression(
            F"Value: value"
            F"Expected type field.type    pour field.name"
            F"got type(value)"
        )
        si type(value) != field.type:
            impression("Type Error")
        autre:
            impression("Type Ok")

def validate(args: liste[[[[str]):
    # If passed to the command line, need to convert
    # the optional 3rd argument from string to int
    si len(args) > 2 et args[[[[2].isdigit():
        args[[[[2] = int(args[[[[2])
    try:
        arguments = Arguments(*args)
    except TypeError:
        raise SystemExit(USAGE)
    check_type(arguments)

def main() -> Aucun:
    args = sys.argv[[[[1:]
    si ne pas args:
        raise SystemExit(USAGE)

    si args[[[[0] == "--help":
        impression(USAGE)
    autre:
        validate(args)

si __name__ == "__main__":
    main()

Unless you pass the --help option at the command line, this script expects two or three arguments:

  1. A mandatory string: firstname
  2. A mandatory string: lastname
  3. An optional integer: age

Because all the items in sys.argv are strings, you need to convert the optional third argument to an integer if it’s composed of digits. str.isdigit() validates if all the characters in a string are digits. In addition, by constructing the data class Arguments with the values of the converted arguments, you obtain two validations:

  1. If the number of arguments doesn’t correspond to the number of mandatory fields expected by Arguments, then you get an error. This is a minimum of two and a maximum of three fields.
  2. If the types after conversion aren’t matching the types defined in the Arguments data class definition, then you get an error.

You can see this in action with the following execution:

$ python val_type_dc.py Guido "Van Rossum" 25
Value: Guido, Expected type  for firstname, got 
Type Ok
Value: Van Rossum, Expected type  for lastname, got 
Type Ok
Value: 25, Expected type  for age, got 
Type Ok

In the execution above, the number of arguments is correct and the type of each argument is also correct.

Now, execute the same command but omit the third argument:

$ python val_type_dc.py Guido "Van Rossum"
Value: Guido, Expected type  for firstname, got 
Type Ok
Value: Van Rossum, Expected type  for lastname, got 
Type Ok
Value: 0, Expected type  for age, got 
Type Ok

The result is also successful because the field age is defined with a default value, 0, so the data class Arguments doesn’t require it.

On the contrary, if the third argument isn’t of the proper type—say, a string instead of integer—then you get an error:

python val_type_dc.py Guido Van Rossum
Value: Guido, Expected type  for firstname, got 
Type Ok
Value: Van, Expected type  for lastname, got 
Type Ok
Value: Rossum, Expected type  for age, got 
Type Error

The expected value Van Rossum, isn’t surrounded by quotes, so it’s split. The second word of the last name, Rossum, is a string that’s handled as the age, which is expected to be an int. The validation fails.

Similarly, you could also use a NamedTuple to achieve a similar validation. You’d replace the data class with a class deriving from NamedTuple, et check_type() would change as follows:

de typing importation NamedTuple

class Arguments(NamedTuple):
    firstname: str
    lastname: str
    age: int = 0

def check_type(obj):
    pour attr, value dans obj._asdict().articles():
        impression(
            F"Value: value"
            F"Expected type obj.__annotations__[attr]    pour attr"
            F"got type(value)"
        )
        si type(value) != obj.__annotations__[[[[attr]:
            impression("Type Error")
        autre:
            impression("Type Ok")

UNE NamedTuple exposes functions like _asdict that transform the object into a dictionary that can be used for data lookup. It also exposes attributes like __annotations__, which is a dictionary storing types for each field, and For more on __annotations__, check out Python Type Checking (Guide).

As highlighted in Python Type Checking (Guide), you could also leverage existing packages like Enforce, Pydantic, and Pytypes for advanced validation.

Custom Validation

Not unlike what you’ve already explored earlier, detailed validation may require some custom approaches. For example, if you attempt to execute sha1sum_stdin.py with an incorrect file name as an argument, then you get the following:

$ python sha1sum_stdin.py bad_file.txt
Traceback (most recent call last):
        File "sha1sum_stdin.py", line 32, in 
                main(sys.argv[1:])
        File "sha1sum_stdin.py", line 29, in main
                output_sha1sum(process_file(arg), arg)
        File "sha1sum_stdin.py", line 9, in process_file
                return pathlib.Path(filename).read_bytes()
        File "/usr/lib/python3.8/pathlib.py", line 1222, in read_bytes
                with self.open(mode='rb') as f:
        File "/usr/lib/python3.8/pathlib.py", line 1215, in open
                return io.open(self, mode, buffering, encoding, errors, newline,
        File "/usr/lib/python3.8/pathlib.py", line 1071, in _opener
                return self._accessor.open(self, flags, mode)
FileNotFoundError: [Errno 2] No such file or directory: 'bad_file.txt'

bad_file.txt doesn’t exist, but the program attempts to read it.

Revisit main() dans sha1sum_stdin.py to handle non-existing files passed at the command line:

    1 def main(args):
    2     si ne pas args:
    3         output_sha1sum(process_stdin())
    4     pour arg dans args:
    5         si arg == "-":
    6             output_sha1sum(process_stdin(), "-")
    sept             continuer
    8         try:
    9             output_sha1sum(process_file(arg), arg)
dix         except FileNotFoundError as err:
11             impression(F"sys.argv[0]: arg: err.strerror", fichier=sys.stderr)

To see the complete example with this extra validation, expand the code block below:

# sha1sum_val.py

de typing importation liste
importation hashlib
importation pathlib
importation sys

def process_file(filename: str) -> bytes:
    return pathlib.Chemin(filename).read_bytes()

def process_stdin() -> bytes:
    return bytes("".join(sys.stdin), "utf-8")

def sha1sum(Les données: bytes) -> str:
    m = hashlib.sha1()
    m.mise à jour(Les données)
    return m.hexdigest()

def output_sha1sum(Les données: bytes, filename: str = "-") -> Aucun:
    impression(F"sha1sum(data)  filename")

def main(args: liste[[[[str]) -> Aucun:
    si ne pas args:
        output_sha1sum(process_stdin())
    pour arg dans args:
        si arg == "-":
            output_sha1sum(process_stdin(), "-")
            continuer
        try:
            output_sha1sum(process_file(arg), arg)
        except (FileNotFoundError, IsADirectoryError) as err:
            impression(F"sys.argv[0]: arg: err.strerror", fichier=sys.stderr)

si __name__ == "__main__":
    main(sys.argv[[[[1:])

When you execute this modified script, you get this:

$ python sha1sum_val.py bad_file.txt
sha1sum_val.py: bad_file.txt: No such file or directory

Note that the error displayed to the terminal is written to stderr, so it doesn’t interfere with the data expected by a command that would read the output of sha1sum_val.py:

$ python sha1sum_val.py bad_file.txt main.py | cut -d " " -F 1
sha1sum_val.py: bad_file.txt: No such file or directory
d84372fc77a90336b6bb7c5e959bcb1b24c608b4

This command pipes the output of sha1sum_val.py à Couper to only include the first field. You can see that Couper ignores the error message because it only receives the data sent to stdout.

The Python Standard Library

Despite the different approaches you took to process Python command line arguments, any complex program might be better off leveraging existing libraries to handle the heavy lifting required by sophisticated command line interfaces. As of Python 3.7, there are three command line parsers in the standard library:

  1. argparse
  2. getopt
  3. optparse

The recommended module to use from the standard library is argparse. The standard library also exposes optparse but it’s officially deprecated and only mentioned here for your information. It was superseded by argparse in Python 3.2 and you won’t see it discussed in this tutorial.

argparse

You’re going to revisit sha1sum_val.py, the most recent clone of sha1sum, to introduce the benefits of argparse. To this effect, you’ll modify main() et ajouter init_argparse to instantiate argparse.ArgumentParser:

    1 importation argparse
    2 
    3 def init_argparse() -> argparse.ArgumentParser:
    4     parser = argparse.ArgumentParser(
    5         usage="%(prog)s [OPTION] [FILE]...",
    6         description="Print or check SHA1 (160-bit) checksums."
    sept     )
    8     parser.add_argument(
    9         "-v", "--version", action="version",
dix         version = F"parser.prog    version 1.0.0"
11     )
12     parser.add_argument('files', nargs='*')
13     return parser
14 
15 def main() -> Aucun:
16     parser = init_argparse()
17     args = parser.parse_args()
18     si ne pas args.files:
19         output_sha1sum(process_stdin())
20     pour fichier dans args.files:
21         si fichier == "-":
22             output_sha1sum(process_stdin(), "-")
23             continuer
24         try:
25             output_sha1sum(process_file(fichier), fichier)
26         except (FileNotFoundError, IsADirectoryError) as err:
27             impression(F"sys.argv[0]: file: err.strerror", fichier=sys.stderr)

For the cost of a few more lines compared to the previous implementation, you get a clean approach to add --help et --version options that didn’t exist before. The expected arguments (the files to be processed) are all available in field files of object argparse.Namespace. This object is populated on line 17 by calling parse_args().

To look at the full script with the modifications described above, expand the code block below:

# sha1sum_argparse.py

importation argparse
importation hashlib
importation pathlib
importation sys

def process_file(filename: str) -> bytes:
    return pathlib.Chemin(filename).read_bytes()

def process_stdin() -> bytes:
    return bytes("".join(sys.stdin), "utf-8")

def sha1sum(Les données: bytes) -> str:
    sha1_hash = hashlib.sha1()
    sha1_hash.mise à jour(Les données)
    return sha1_hash.hexdigest()

def output_sha1sum(Les données: bytes, filename: str = "-") -> Aucun:
    impression(F"sha1sum(data)  filename")

def init_argparse() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        usage="%(prog)s [OPTION] [FILE]...",
        description="Print or check SHA1 (160-bit) checksums.",
    )
    parser.add_argument(
        "-v", "--version", action="version",
        version=F"parser.prog    version 1.0.0"
    )
    parser.add_argument("files", nargs="*")
    return parser

def main() -> Aucun:
    parser = init_argparse()
    args = parser.parse_args()
    si ne pas args.files:
        output_sha1sum(process_stdin())
    pour fichier dans args.files:
        si fichier == "-":
            output_sha1sum(process_stdin(), "-")
            continuer
        try:
            output_sha1sum(process_file(fichier), fichier)
        except (FileNotFoundError, IsADirectoryError) as err:
            impression(F"parser.prog: file: err.strerror", fichier=sys.stderr)

si __name__ == "__main__":
    main()

To illustrate the immediate benefit you obtain by introducing argparse in this program, execute the following:

$ python sha1sum_argparse.py --help
usage: sha1sum_argparse.py [OPTION] [FILE]...

Print or check SHA1 (160-bit) checksums.

positional arguments:
        files

optional arguments:
        -h, --help     show this help message and exit
        -v, --version  show program's version number and exit

To delve into the details of argparse, check out How to Build Command Line Interfaces in Python With argparse.

getopt

getopt finds its origins in the getopt C function. It facilitates parsing the command line and handling options, option arguments, and arguments. Revisit parse de seq_parse.py to use getopt:

def parse():
    les options, arguments = getopt.getopt(
        sys.argv[[[[1:],                      # Arguments
        'vhs:',                            # Short option definitions
        [[[["version", "help", "separator="]) # Long option definitions
    separator = "n"
    pour o, une dans les options:
        si o dans ("-v", "--version"):
            impression(VERSION)
            sys.exit()
        si o dans ("-h", "--help"):
            impression(USAGE)
            sys.exit()
        si o dans ("-s", "--separator"):
            separator = une
    si ne pas arguments ou len(arguments) > 3:
        raise SystemExit(USAGE)
    try:
        operands = [[[[int(arg) pour arg dans arguments]
    except ValueError:
        raise SystemExit(USAGE)
    return separator, operands

getopt.getopt() takes the following arguments:

  1. The usual arguments list minus the script name, sys.argv[1:]
  2. A string defining the short options
  3. A list of strings for the long options

Note that a short option followed by a colon (:) expects an option argument, and that a long option trailed with an equals sign (=) expects an option argument.

The remaining code of seq_getopt.py is the same as seq_parse.py and is available in the collapsed code block below:

# seq_getopt.py

de typing importation liste, Tuple
importation getopt
importation sys

USAGE = F"Usage: python sys.argv[0] [--help]    |[-s[-s[-s[-s] [first [incr]]last"
VERSION = F"sys.argv[0]    version 1.0.0"

def seq(operands: liste[[[[int], sep: str = "n") -> str:
    first, increment, last = 1, 1, 1
    si len(operands) == 1:
        last = operands[[[[0]
    elif len(operands) == 2:
        first, last = operands
        si first > last:
            increment = -1
    elif len(operands) == 3:
        first, increment, last = operands
    last = last - 1 si first > last autre last + 1
    return sep.join(str(je) pour je dans range(first, last, increment))

def parse(args: liste[[[[str]) -> Tuple[[[[str, liste[[[[int]]:
    les options, arguments = getopt.getopt(
        args,                              # Arguments
        'vhs:',                            # Short option definitions
        [[[["version", "help", "separator="]) # Long option definitions
    separator = "n"
    pour o, une dans les options:
        si o dans ("-v", "--version"):
            impression(VERSION)
            sys.exit()
        si o dans ("-h", "--help"):
            impression(USAGE)
            sys.exit()
        si o dans ("-s", "--separator"):
            separator = une
    si ne pas arguments ou len(arguments) > 3:
        raise SystemExit(USAGE)
    try:
        operands = [[[[int(arg) pour arg dans arguments]
    except:
        raise SystemExit(USAGE)
    return separator, operands

def main() -> Aucun:
    args = sys.argv[[[[1:]
    si ne pas args:
        raise SystemExit(USAGE)
    sep, operands = parse(args)
    impression(seq(operands, sep))

si __name__ == "__main__":
    main()

Next, you’ll take a look at some external packages that will help you parse Python command line arguments.

A Few External Python Packages

Building upon the existing conventions you saw in this tutorial, there are a few libraries available on the Python Package Index (PyPI) that take many more steps to facilitate the implementation and maintenance of command line interfaces.

The following sections offer a glance at Click and Python Prompt Toolkit. You’ll only be exposed to very limited capabilities of these packages, as they both would require a full tutorial—if not a whole series—to do them justice!

Cliquez sur

As of this writing, Cliquez sur is perhaps the most advanced library to build a sophisticated command line interface for a Python program. It’s used by several Python products, most notably Flask and Black. Before you try the following example, you need to install Click in either a Python virtual environment or your local environment. If you’re not familiar with the concept of virtual environments, then check out Python Virtual Environments: A Primer.

To install Click, proceed as follows:

$ python -m pip install click

So, how could Click help you handle the Python command line arguments? Here’s a variation of the seq program using Click:

# seq_click.py

importation click

@click.command(context_settings=dict(ignore_unknown_options=True))
@click.option("--separator", "-s",
              default="n",
              help="Text used to separate numbers (default: \n)")
@click.version_option(version="1.0.0")
@click.argument("operands", type=click.INT, nargs=-1)
def seq(operands, separator) -> str:
    first, increment, last = 1, 1, 1
    si len(operands) == 1:
        last = operands[[[[0]
    elif len(operands) == 2:
        first, last = operands
        si first > last:
            increment = -1
    elif len(operands) == 3:
        first, increment, last = operands
    autre:
        raise click.BadParameter("Invalid number of arguments")
    last = last - 1 si first > last autre last + 1
    impression(separator.join(str(je) pour je dans range(first, last, increment)))

si __name__ == "__main__":
    seq()

Réglage ignore_unknown_options à True ensures that Click doesn’t parse negative arguments as options. Negative integers are valid seq arguments.

As you may have observed, you get a lot for free! A few well-carved decorators are sufficient to bury the boilerplate code, allowing you to focus on the main code, which is the content of seq() in this example.

The only import remaining is click. The declarative approach of decorating the main command, seq(), eliminates repetitive code that’s otherwise necessary. This could be any of the following:

  • Defining a help or usage procedure
  • Handling the version of the program
  • Capture et setting up default values for options
  • Validating arguments, including the type

Le nouveau seq implementation barely scratches the surface. Click offers many niceties that will help you craft a very professional command line interface:

  • Output coloring
  • Prompt for omitted arguments
  • Commands and sub-commands
  • Argument type validation
  • Callback on options and arguments
  • File path validation
  • Progress bar

There are many other features as well. Check out Writing Python Command-Line Tools With Click to see more concrete examples based on Click.

Python Prompt Toolkit

There are other popular Python packages that are handling the command line interface problem, like docopt for Python. So, you may find the choice of the Prompt Toolkit a bit counterintuitive.

le Python Prompt Toolkit provides features that may make your command line application drift away from the Unix philosophy. However, it helps to bridge the gap between an arcane command line interface and a full-fledged graphical user interface. In other words, it may help to make your tools and programs more user-friendly.

You can use this tool in addition to processing Python command line arguments as in the previous examples, but this gives you a path to a UI-like approach without you having to depend on a full Python UI toolkit. To use prompt_toolkit, you need to install it with pip:

$ python -m pip install prompt_toolkit

You may find the next example a bit contrived, but the intent is to spur ideas and move you slightly away from more rigorous aspects of the command line with respect to the conventions you’ve seen in this tutorial.

As you’ve already seen the core logic of this example, the code snippet below only presents the code that significantly deviates from the previous examples:

def error_dlg():
    message_dialog(
        Titre="Error",
        text="Ensure that you enter a number",
    ).courir()

def seq_dlg():
    labels = [[[["FIRST", "INCREMENT", "LAST"]
    operands = []
    while True:
        n = input_dialog(
            Titre="Sequence",
            text=F"Enter argument labels[len(operands)]:",
        ).courir()
        si n est Aucun:
            Pause
        si n.isdigit():
            operands.append(int(n))
        autre:
            error_dlg()
        si len(operands) == 3:
            Pause

    si operands:
        seq(operands)
    autre:
        impression("Bye")        

actions = "SEQUENCE": seq_dlg, "HELP": help, "VERSION": version

def main():
    result = button_dialog(
        Titre="Sequence",
        text="Select an action:",
        buttons=[[[[
            ("Sequence", "SEQUENCE"),
            ("Help", "HELP"),
            ("Version", "VERSION"),
        ],
    ).courir()
    actions.avoir(result, lambda: impression("Unexpected action"))()

The code above involves ways to interact and possibly guide users to enter the expected input, and to validate the input interactively using three dialog boxes:

  1. button_dialog
  2. message_dialog
  3. input_dialog

The Python Prompt Toolkit exposes many other features intended to improve interaction with users. The call to the handler in main() is triggered by calling a function stored in a dictionary. Check out Emulating switch/case Statements in Python if you’ve never encountered this Python idiom before.

You can see the full example of the program using prompt_toolkit by expanding the code block below:

# seq_prompt.py

importation sys
de typing importation liste
de prompt_toolkit.shortcuts importation button_dialog, input_dialog, message_dialog

def version():
    impression("Version 1.0.0")

def help():
    impression("Print numbers from FIRST to LAST, in steps of INCREMENT.")

def seq(operands: liste[[[[int], sep: str = "n"):
    first, increment, last = 1, 1, 1
    si len(operands) == 1:
        last = operands[[[[0]
    elif len(operands) == 2:
        first, last = operands
        si first > last:
            increment = -1
    elif len(operands) == 3:
        first, increment, last = operands
    last = last - 1 si first > last autre last + 1
    impression(sep.join(str(je) pour je dans range(first, last, increment)))

def error_dlg():
    message_dialog(
        Titre="Error",
        text="Ensure that you enter a number",
    ).courir()

def seq_dlg():
    labels = [[[["FIRST", "INCREMENT", "LAST"]
    operands = []
    while True:
        n = input_dialog(
            Titre="Sequence",
            text=F"Enter argument labels[len(operands)]:",
        ).courir()
        si n est Aucun:
            Pause
        si n.isdigit():
            operands.append(int(n))
        autre:
            error_dlg()
        si len(operands) == 3:
            Pause

    si operands:
        seq(operands)
    autre:
        impression("Bye")        

actions = "SEQUENCE": seq_dlg, "HELP": help, "VERSION": version

def main():
    result = button_dialog(
        Titre="Sequence",
        text="Select an action:",
        buttons=[[[[
            ("Sequence", "SEQUENCE"),
            ("Help", "HELP"),
            ("Version", "VERSION"),
        ],
    ).courir()
    actions.avoir(result, lambda: impression("Unexpected action"))()

si __name__ == "__main__":
    main()

When you execute the code above, you’re greeted with a dialog prompting you for action. Then, if you choose the action Sequence, another dialog box is displayed. After collecting all the necessary data, options, or arguments, the dialog box disappears, and the result is printed at the command line, as in the previous examples:

Prompt Toolkit Example

As the command line evolves and you can see some attempts to interact with users more creatively, other packages like PyInquirer also allow you to capitalize on a very interactive approach.

To further explore the world of the Text-Based User Interface (TUI), check out Building Console User Interfaces and the Third Party section in Your Guide to the Python Print Function.

If you’re interested in researching solutions that rely exclusively on the graphical user interface, then you may consider checking out the following resources:

Conclusion

In this tutorial, you’ve navigated many different aspects of Python command line arguments. You should feel prepared to apply the following skills to your code:

  • le conventions and pseudo-standards of Python command line arguments
  • le origins de sys.argv in Python
  • le usage de sys.argv to provide flexibility in running your Python programs
  • le Python standard libraries comme argparse ou getopt that abstract command line processing
  • le powerful Python packages comme click et python_toolkit to further improve the usability of your programs

Whether you’re running a small script or a complex text-based application, when you expose a command line interface you’ll significantly improve the user experience of your Python software. In fact, you’re probably one of those users!

Next time you use your application, you’ll appreciate the documentation you supplied with the --help option or the fact that you can pass options and arguments instead of modifying the source code to supply different data.

Additional Resources

To gain further insights about Python command line arguments and their many facets, you may want to check out the following resources:

You may also want to try other Python libraries that target the same problems while providing you with different solutions: