Ligne de commande avec Python

Lorsqu’on écrit un script ou un programme en Python, ce n’est pas nécessairement pour en faire un usage unique. Il est assez commun de réutiliser le même programme en faisant varier certains paramètres : par exemple, le chemin d’accès d’un fichier d’entrée, la valeur d’une variable prise en paramètre par une fonction, l’application ou non d’une condition… Ce sont des cas où il peut s’avérer préférable de ne pas modifier à répétition le code source. Une solution pour prendre en compte les variations à appliquer aux multiples exécutions du programme est de mettre en place une interaction avec la ligne de commande.

C’est ce que permet de faire le module argparse, et ce n’est pas compliqué ! Voici une illustration de son fonctionnement, avec un exemple d’étiquetage syntaxique d’un texte en chinois.

Le script présenté ne sera pas intégralement reproduit dans l’article. Vous pouvez le télécharger librement avec son exemple de fichier d’entrée1 sur Github ainsi que sur Gitlab.

Plusieurs articles de ce carnet ont été consacrés à la segmentation et à l’étiquetage de textes en japonais ou en chinois. De nombreux segmenteurs/étiqueteurs existent et il peut être pertinent d’en tester plusieurs lors de la constitution d’un corpus, à titre de comparaison ou pour enrichir l’annotation du corpus à partir d’informations issues de différents étiquetages.

Objectifs

Nous allons ici écrire un programme permettant d’étiqueter automatiquement des textes en chinois avec la possibilité de faire un choix entre deux segmenteurs, PyNLPIR et Jieba. La ligne de commande va permettre de faire passer les paramètres suivants :

  • Le chemin d’accès du fichier d’entrée contenant le texte à étiqueter.
  • Le segmenteur à utiliser pour segmenter le texte et effectuer l’étiquetage syntaxique.

Prérequis

Puisque les arguments de la ligne de commande vont ici permettre d’étiqueter un texte, une partie du script sera consacrée à l’étiquetage mais ne sera pas expliquée dans cet article. Si ce n’est que l’interaction avec la ligne de commande qui vous intéresse, vous n’avez pas besoin de comprendre le fonctionnement des étiqueteurs ni le code source qui les concerne.

Cependant, Jieba et PyNLPIR ont tous deux fait l’objet d’articles sur Tekipaki. Si la question de l’étiquetage vous intéresse ou si vous voulez comprendre pleinement le script mis à disposition je vous conseille de lire :

Concernant les prérequis techniques, le module argparse fait partie de la librairie standard de Python et n’a pas besoin d’être installé, il peut être directement importé. Si vous voulez reproduire ou utiliser le script téléchargeable, il vous faudra installer les librairies jieba2 et PyNLPIR3.

Le script Python

Dans le script téléchargeable, l’interaction avec la ligne de commande est mise en place dans la fonction recuperer_arguments().

Déclarer un analyseur (parser)

Pour que le script puisse prendre en compte les arguments entrés en ligne de commande, il faut tout d’abord créer une instance d’objet ArgumentParser(), du module argparse. Dans le script en exemple, elle a été nommée parser.

parser = argparse.ArgumentParser()

Cela n’a pas été fait ici, mais comme l’indique la documentation du module, vous pouvez inclure des informations à propos du programme lors de la déclaration de l’instance d’ArgumentParser.

Ajouter les arguments

Il est maintenant possible de choisir les arguments via la méthode .add_argument() de la classe ArgumentParser. Nous avons ici besoin de deux arguments : l’un pour le choix du fichier d’entrée, l’autre pour le choix du segmenteur.

parser.add_argument("--fichier_entree", "-e")
parser.add_argument("--segmenteur", "-s")

La syntaxe montrée ci-dessus est suffisante pour ajouter les deux options, pour lesquelles l’utilisateur entrera la valeur de son choix4. Par exemple :

py jieba_or_pynlpir.py --fichier_entree ./input.txt --segmenteur jieba

Ou, pour le même résultat :

py jieba_or_pynlpir.py -e ./input.txt -s jieba

Certes, ça fonctionne, mais ce n’est pas vraiment suffisant. Il est en effet possible d’ajouter des options pour affiner le traitement des arguments. Parmi les différentes possibilités, certaines sont particulièrement pertinentes dans le cas présent :

  • Donner une valeur par défaut, afin de pouvoir exécuter le script même sans spécifier l’un ou l’autre des arguments (voire même en ne spécifiant aucun argument).
  • Imposer un type à respecter pour la valeur des arguments (en l’occurrence, des chaînes de caractères puisqu’elles seront ensuite traitées comme telles).
  • Limiter les choix de valeurs possibles. Ce n’est ici pas pertinent pour le chemin d’accès du fichier d’entrée, mais très intéressant dans le cas du choix de segmenteur où il n’y a que deux valeurs disponibles (Jieba ou PyNLPIR).
  • Et enfin, indiquer à quoi correspond chacun des arguments.

Il faut alors déclarer, respectivement, les options default, type, choice et help lors de l’appel à la méthode .add_argument(). Voici alors comment compléter l’ajout de l’argument --segmenteur :

# Ajout d'un argument pour choix du segmenteur, entre Jieba et PyNLPIR
parser.add_argument("--segmenteur", "-s",
                    help="Choix du segmenteur (jieba ou j pour utiliser Jieba, pynlpir ou p pour utiliser PyNLPIR), défaut = jieba",
                    choices=["j", "jieba", "p", "pynlpir"],
                    default="j",
                    type=str)

Les messages d’aide associés à help peuvent être affichés avec la commande --help ou -h.

py jieba_or_pynlpir.py --help
Message d'aide du script

Si la valeur donnée pour le choix de segmenteur ne figure pas dans la liste des valeurs autorisées déclarées dans choice, un message d’erreur sera alors affiché.

Par ailleurs, puisque l’option default est renseignée pour les deux arguments déclarés, il est possible d’exécuter le script sans spécifier d’argument. Ce sont les valeurs par défaut qui seront utilisées : le script prendra en entrée le fichier dont le chemin d’accès est ./input.txt et utilisera Jieba pour le segmenter et l’étiqueter.

Exploiter les arguments

Il faut utiliser la méthode .parse_args() des objets ArgumentParser() pour récupérer les valeurs associées aux différents arguments dans la ligne de commande. Dans l’exemple, celle-ci a donc été appliquée à parser et le résultat obtenu est retourné par la fonction recuperer_arguments().

Il est maintenant possible d’utiliser cette fonction dans le script pour récupérer les arguments de la ligne de commande et prendre en compte leurs valeurs respectives lors des traitements qui seront mis en place.

arguments = recuperer_arguments()

La variable arguments, correspondant au résultat de la fonction recuperer_arguments(), peut ainsi être utilisée pour manipuler les valeurs données aux arguments, soit ici le chemin du fichier d’entrée et le segmenteur. Comme d’habitude, le nom donné à la variable a été choisi arbitrairement, vous pouvez choisir un nom différent si vous écrivez votre propre script.

La syntaxe à utiliser pour récupérer la valeur associée à un argument donné est arguments.nom_complet_de_l_argument :

arguments.fichier_entree
arguments.segmenteur

Conformément à la déclaration des arguments dans le parseur, il s’agit de deux chaînes de caractères :

  • arguments.fichier_entree doit correspondre au chemin d’accès du fichier d’entrée, dont le contenu sera lu et traité par le script. Sa valeur est choisie librement par l’utilisateur.
    • arguments.segmenteur correspond au choix de segmenteur. Sa valeur n’est pas libre et est à décider entre j, jieba, p et pynlpir.

Il est possible de leur appliquer tout traitement compatible avec les chaînes de caractères (type str). Dans l’exemple, arguments.fichier_entree est utilisé dans with open() afin de lire le fichier correspondant au chemin spécifié. arguments.segmenteur est examiné via une condition if permettant de déterminer si l’utilisateur souhaite segmenter le texte avec Jieba (j, jieba) ou PyNLPIR (p, pynlpir).

La structure globale de cette partie du script est finalement très simple :

    with open(arguments.fichier_entree, "r", encoding="utf-8") as source:

        jieba_args = ["jieba", "j"]
        pynlpir_args = ["pynlpir", "p"]

        if arguments.segmenteur in jieba_args:
            # Segmenter le contenu du fichier avec Jieba
        elif arguments.segmenteur in pynlpir_args:
            # Segmenter le contenu du fichier avec PyNLPIR

En conservant la valeur par défaut de l’argument --fichier_entree (-e), le texte5 sera segmenté par Jieba avec la commande suivante :

py jieba_or_pynlpir.py -s j

Puisque Jieba est le choix par défaut, la commande suivante est équivalente :

py jieba_or_pynlpir.py

Voici les premières lignes de la sortie obtenue :

**SEGMENTATION ET ÉTIQUETAGE DU FICHIER ./input.txt AVEC Jieba**
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\USER~1\AppData\Local\Temp\jieba.cache
Loading model cost 0.504 seconds.
Prefix dict has been built successfully.
皇帝/n 企鹅/n 的/uj 分布/v (/x 绿色/n 为/p 繁殖地/n )/x

皇帝/n 企鹅/n (/x 学名/n :/x Aptenodytes/eng  /x forsteri/eng )/x 常/d 简称/v 为/p 帝企鹅/n 或/c 皇/n 企鹅/n ,/x 是/v 企鹅/n 家族/nz 中/f 体型/n 最大/a 的/uj 属种/n [/x 1/x ]/x ,/x 成年/t 皇帝/n 企鹅/n 身高/v 可达/v 120/m 厘米/q ,/x  体重/n 可达/v 46/m 公斤/q 。/x 在/p 皇帝/n 企鹅/n 发现/v 之前/f ,/x 有/v 一种/m 企鹅/n 被/p 认为/v 是/v 最大/a 的/uj 企鹅/n ,/x 取名为/i 国王/n 企鹅/n 。/x 后来/t ,/x 在/p 南极/ns 大陆/n 沿海/f 皇帝/n 企鹅/n 被/p 发现/v ,/x 由于/c 它/r 比/p 国王/n 企鹅/n 还/d 高大/a ,/x 因此/c 命名/n 为/p 皇帝/n 企鹅/n [/x 2/x ]/x 。/x

公/n 皇帝/n 企鹅/n 雙腿/n 和/c 腹部/n 下方/f 之間/f 有/v 一/m 塊/q 佈/yg 滿/a 血管/n 的/uj 紫色/n 皮膚/n 的/uj 孵卵/v 斑/n ,/x 能/v 讓/v 蛋/n 在/p 環境/n 溫度/n 低達/a 零下/m 華氏/nr 180/m 度/q (/x 攝/vg 氏/ng 零下/m 117/m 度/q )/x 的/uj  低溫/n 中/f 保持/v 在/p 舒適/a 的/uj 華氏/nr 97/m 度/q (/x 攝/vg 氏/ng 36/m 度/q )/x ,/x 小/a 皇帝/n 企鹅/n 身上/s 的/uj 淺/n 灰白色/n 絨羽/n 可禦/v 寒防/n 風/n ,/x 但/c 不/d 防水/v ,/x 防水/v 的/uj 翎羽/n 要/v 等到/v 牠/y 們/k 快/a 成年/t 時/ng 才/d 會/v 長/a 出/v ,/x 慢慢/d 替換/v 身上/s 的/uj 絨羽/n ,/x 身體/n 下方/f 的/uj 絨/n 羽會/n 先/d 掉/v 。/x

目录/n

Le texte sera segmenté avec PyNLPIR en utilisant la commande suivante :

py jieba_or_pynlpir.py -s p

Voici les premières lignes de la sortie obtenue :

**SEGMENTATION ET ÉTIQUETAGE DU FICHIER ./input.txt AVEC PyNLPIR**
皇帝/n 企鹅/n 的/ude1 分布/vn (/wkz 绿色/n 为/p 繁殖地/n )/wky

皇帝/n 企鹅/n (/wkz 学名/n :/wp Aptenodytes/n  /None forsteri/n )/wky 常/d 简称/v 为/v 帝/ng 企鹅/n 或/c 皇/ng 企鹅/n ,/wd 是/vshi 企鹅/n 家族/n 中/b 体型/n 最/d 大/a 的/ude1 属/v 种/q [1]/xm ,/wd 成年/vn 皇帝/n 企鹅/n 身高/n 可/v 达/v 120/m 厘米/q ,/wd 体重/n 可/v 达/v 46/m 公斤/q 。/wj 在/p 皇帝/n 企鹅/n 发现/v 之前/f ,/wd 有/vyou 一/m 种/q 企鹅/n  被/pbei 认为/v 是/vshi 最/d 大/a 的/ude1 企鹅/n ,/wd 取名/v 为/p 国王/n 企鹅/n 。/wj 后来/t ,/wd 在/p 南极/ns 大陆/n  沿海/f 皇帝/n 企鹅/n 被/pbei 发现/v ,/wd 由于/c 它/rr 比/p 国王/n 企鹅/n 还/d 高大/a ,/wd 因此/c 命名/v 为/p 皇帝/n 企鹅/n [2]/xm 。/wj

公/b 皇帝/n 企鹅/n 雙/n 腿/n 和/cc 腹部/n 下方/f 之/uzhi 間/n 有/vyou 一塊佈/m 滿/n 血管/n 的/ude1 紫色/n 皮/n 膚/n 的/ude1 孵卵/vi 斑/n ,/wd 能/v 讓/n 蛋/n 在/p 環/n 境/ng 溫/n 度/qv 低/a 達/n 零下/s 華/n 氏/ng 180/m 度/qv (/wkz 攝/n 氏/ng 零下/s 117/m 度/qv )/wky 的/ude1 低/a 溫/n 中/f 保持/v 在/p 舒/nr1 適/n 的/ude1 華/n 氏/ng 97/m 度/qv (/wkz 攝/n 氏/ng 36/m 度/qv )/wky ,/wd 小/a 皇帝/n 企鹅/n 身上/s 的/ude1 淺/n 灰白色/n 絨/n 羽/ng 可/v 禦/n 寒/ag 防/v 風/n ,/wd 但/c 不/d 防水/vi ,/wd 防水/vi 的/ude1 翎/ng 羽/ng 要/v 等到/v 牠/n 們/n 快/a 成年/vn 時/n 才/c 會/n 長/n 出/vf ,/wd 慢慢/d 替/p 換/n 身上/s 的/ude1 絨/n 羽/ng ,/wd 身/ng 體/n 下方/f 的/ude1 絨/n 羽/q 會/n 先/d 掉/v 。/wj

目录/n

Aller plus loin

Le script téléchargeable est un exemple très simple d’exploitation des arguments de la ligne de commande. Voici quelques idées d’améliorations à implémenter.

Gestion des erreurs

Comme il n’y a pas de contrôle sur la valeur passée à l’argument fichier_entree, il est possible de donner un chemin d’accès ne correspondant à aucun fichier. L’exécution aboutira alors sur une erreur FileNotFoundError. Vous pouvez mettre en place un try/except pour gérer cette erreur, par exemple en permettant à l’utilisateur d’entrer un nouveau chemin d’accès plutôt que de laisser le script planter.

Données de sortie

En testant le script téléchargeable, vous avez peut-être obtenu un résultat ressemblant à ceci :

Terminal avec police de caractères Consolas où les caractères chinois sont remplacés par des points d'interrogation.

Il s’agit de l’extrait d’équiquetage du texte avec PyNLPIR montré précédemment , mais il y a manifestement un problème d’affichage des caractères. La cause du problème est que, dans ce terminal Windows utilisant la police Consolas, les caractères sont manquants. Le script a pourtant fonctionné correctement, il s’agit bien uniquement d’un problème de police de caractères : si vous obtenez ce résultat, vous pouvez d’ailleurs le copier-coller dans un éditeur de texte utilisant une police où ils sont reconnus, et ils s’afficheront correctement.

Une autre solution serait de modifier la police d’affichage dans le terminal. Par exemple, en passant le terminal montré ci-dessus en police NSimSun :

Terminal utilisant la police NSimSun affichant correctement les caractères chinois.

Cependant, remplacer ou compléter les print() par l’écriture des résultats dans un fichier constituerait une solution bien plus pratique. Il est même possible d’ajouter un argument à la ligne de commande pour laisser à l’utilisateur le choix du chemin d’accès de ce fichier de sortie.

Données d’entrée

À l’inverse, le script n’accepte qu’un fichier en entrée. Selon les données à traiter, il pourrait être souhaitable de varier les entrées prises en charge :

  • Traiter une simple chaîne de caractère pourrait par exemple permettre de tester rapidement le programme avant de lui soumettre un fichier.
  • Si le nombre de fichiers à traiter est important, il est plus efficace de faire en sorte que le programme puisse traiter tous les fichier d’un répertoire plutôt que d’exécuter manuellement le programme sur chaque document.
  • L’exemple montré ici ne porte que sur des fichiers .txt, mais rien n’empêche d’utiliser d’autres formats (tant en entrée qu’en sortie) en adaptant les traitements appliqués.

Et bien entendu, en vous inspirant de cet exemple vous pouvez paramétrer votre ligne de commande et ses arguments selon les besoins de votre propre projet !

  1. Son contenu est identique à celui utilisé dans cet article à propos de l’étiquetage avec spaCy. []
  2. Dont vous pouvez consulter la documentation sur cette page. []
  3. Dont vous pouvez consulter la documentation sur cette page. []
  4. Les commandes montrées dans cet article ont été exécutées dans le terminal d’une machine sous Windows 10. Selon votre système d’exploitation et/ou votre installation de Python, les commandes ne seront pas forcément à préfixer par py mais éventuellement par python ou python3. []
  5. Du fichier dont le chemin d’accès est ./input.txt, soit le fichier fourni en exemple si vous téléchargez le code sans le modifier. []

OpenEdition vous propose de citer ce billet de la manière suivante :
Alexander Delaporte (8 décembre 2023). Ligne de commande avec Python. Tekipaki. Consulté le 16 juillet 2024 à l’adresse https://doi.org/10.58079/up2x


Auteur/autrice : Alexander Delaporte

Ingénieur d'études au Centre de recherches linguistiques sur l'Asie Orientale

Une réflexion sur « Ligne de commande avec Python »

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Rechercher dans OpenEdition Search

Vous allez être redirigé vers OpenEdition Search