Création d’une application console
L’élaboration d’une application console est une étape primordiale dans l’apprentissage de la programmation orientée objet, mais avant de plonger dans la conception d’une telle interface, il faut de bien structurer sa solution. La conception d’une application console n’est pas simplement un exercice de programmation ; c’est un défi de design, nécessitant une planification minutieuse et une compréhension approfondie des objets que vous avez créés et de leur fonctionnement.
Avant de commencer à concevoir une interface console, il est fortement recommandé de commencer par l’implémentation des objets de base et de valider leurs fonctionnements de façon isolée. Ceci vous permettra de tester individuellement chaque objet, de résoudre les problèmes avant de commencer d’interagir avec d’autres objets, et de comprendre les flux de données et les relations entre eux.
Les applications console peuvent prendre plusieurs formes, et il est important de connaître les différentes options à votre disposition avant de commencer la conception. Il existe deux types principaux d’applications consoles : les applications basées sur des arguments et les applications basées sur des menus.
- Les applications consoles basées sur des arguments sont très fréquentes, en particulier dans le domaine de la gestion de serveur. Ces applications sont généralement exécutées à partir de la ligne de commande, avec divers arguments passés lors de l’exécution pour déterminer le comportement du programme. Cela peut être une approche très efficace pour les tâches qui nécessitent un traitement par lots ou qui sont automatisées.
- Les applications consoles basées sur des menus offrent une interface utilisateur plus intuitive, en présentant à l’utilisateur une série d’options parmi lesquelles il peut choisir. Ces applications sont souvent plus interactives et peuvent être plus conviviales pour ceux qui ne sont pas aussi à l’aise avec la ligne de commande.
Structure générale d’une application basée sur des menus
Une application console basée sur des menus fonctionne en présentant à l’utilisateur une série d’options parmi lesquelles il peut choisir. Chaque option est généralement associée à une commande ou à une série de commandes exécutées lorsque l’option est sélectionnée. Les options de menu peuvent être présentées sous forme de liste numérotée ou de lettres, et l’utilisateur sélectionne une option en entrant le numéro ou la lettre correspondante.
L’analyse top-down, aussi connue sous le nom de décomposition fonctionnelle, est une méthode de conception de programme qui commence par décrire le système à un haut niveau de fonctionnalité, puis le décompose en sous-fonctions plus petites et plus gérables. Cette approche est particulièrement utile dans le cadre de la conception d’une application console basée sur des menus, où chaque option de menu peut être considérée comme une fonction ou une sous-fonction du système global.
Lorsque vous concevez une application de menu avec une approche top-down, vous commencez par définir le menu principal. Chaque option du menu principal correspond soit à un appel de fonction, soit à un autre sous-menu. Si une option mène à un sous-menu, ce sous-menu peut ensuite être analysé de la même manière, avec chaque option correspondant à un autre appel de fonction ou à un autre sous-menu. Ce processus continue jusqu’à ce que chaque option de menu mène à une fonction spécifique qui réalise une tâche définie.
Cette approche top-down facilite la gestion de la complexité dans la conception de votre application, car elle vous permet de vous concentrer sur une seule fonction ou sous-fonction à la fois. De plus, elle favorise la réutilisation du code, car chaque fonction est définie de manière indépendante et peut être appelée à partir de n’importe quel point de votre application.
Par exemple, supposons que vous concevez une application de gestion de bibliothèque. Au niveau le plus élevé, votre menu principal pourrait avoir des options pour gérer les utilisateurs, gérer les livres ou quitter l’application. Dans le menu « gérer les utilisateurs », nous pourrions avoir un sous-menu pour ajouter ou supprimer un utilisateur.
- Gérer les utilisateurs
- Ajouter un utilisateur
- Supprimer un utilisateur
- Revenir au menu principal
- Gérer les livres
- Quitter l’application
Le code correspondant au menu principale est le suivant :
class ConsoleMain: def __init__(): self.all_users = [] def gerer_utilisateurs(self): user = ConsoleUtilisateur(self.all_users) user.run() def gerer_livres(self): print("Fonctionnalité pour gérer les livres.") def run(self): while True: print("Menu Principal:") print("1. Gérer les utilisateurs") print("2. Gérer les livres") print("3. Quitter l'application") choix = input("Entrez le numéro de votre choix: ") if choix == '1': self.gerer_utilisateurs() elif choix == '2': self.gerer_livres() elif choix == '3': print("Fin du programme.") return else: print("Choix invalide. Veuillez choisir une option valide.") if __name__ == "__main__": main = ConsoleMain() main.run()
Le code correspondant à l’option « Gérer les utilisateurs » est le suivant :
class ConsoleUtilisateur: def __init__(all_users): self.all_users = all_users def ajouter_utilisateur(): print("Fonctionnalité pour ajouter un utilisateur.") def supprimer_utilisateur(): print("Fonctionnalité pour supprimer un utilisateur.") def run(): while True: print("Gérer les utilisateurs:") print("1. Ajouter un utilisateur") print("2. Supprimer un utilisateur") print("3. Revenir au menu principal") choix = input("Entrez le numéro de votre choix: ") if choix == '1': self.ajouter_utilisateur() elif choix == '2': self.supprimer_utilisateur() elif choix == '3': print("Retour au menu principal.") return else: print("Choix invalide. Veuillez choisir une option valide.")
Il est important de comprendre l’importance du return dans la boucle while de nos menus. C’est ce return qui permet de quitter le sous-menu et de revenir au menu principal. Quand l’utilisateur choisit l’option « Revenir au menu principal », l’exécution de la fonction gerer_utilisateurs() est terminée grâce au return, ce qui a pour effet de quitter la boucle while.
Cette instruction return permet d’éviter une erreur courante dans la gestion des sous-menus. Une pratique incorrecte consiste à appeler recréer un objet ConsoleMain
pour revenir au menu principal. Cela peut sembler logique, mais cela crée en fait une nouvelle instance, ce qui peut entraîner une série de problèmes (à cause de l’empilement des appels). Au lieu de cela, en utilisant return, nous terminons simplement l’exécution du sous-menu, ce qui permet proprement de revenir à la boucle principale. Cela maintient l’exécution du programme propre et préserve la structure de notre application.
Par ailleurs, notez l’exploitation du constructeur de la programmation orientée objet qui procure un avantage significatif pour le passage d’informations lors de la création d’applications console. Dans le contexte d’une application console, ces paramètres peuvent servir à transmettre des informations d’un menu à un autre, facilitant ainsi la navigation et l’interaction de l’utilisateur avec l’application.
Respectez l’encapsulation
Quoi qu’il en soit, il est à noter que ces interactions doivent être pensées pour respecter le principe d’encapsulation de la programmation orientée objet. En effet, les classes Consoles ne doivent pas accéder directement aux attributs internes de vos objets, mais plutôt utiliser leurs méthodes publiques pour interagir avec eux. Cela permet de préserver l’intégrité de vos objets et de faciliter la maintenance de votre code.
Il faut souligner que les principes établis pour coder nos classes métiers (c’est-à-dire les classes et objets conçus pour gérer nos données et nos algorithmes), s’appliquent également à la programmation avec nos objets consoles. En particulier, il est vital de préserver la distinction entre la logique métier de votre application et l’interface utilisateur. Ainsi, comme nous avons évité d’utiliser la fonction print dans nos classes métier, il est également interdit d’invoquer un objet Console directement à partir de ces classes lors de leur utilisation. En d’autres termes, vos classes métier ne doivent pas être conscientes de l’existence de l’interface utilisateur et ne devraient contenir aucune référence à un objet Console.
Structure générale d’une application basée sur des arguments
Une application basée sur des arguments, couramment utilisée dans le contexte de la gestion de serveurs ou pour des tâches automatisées, prend des arguments à la ligne de commande pour influencer son comportement. Pour commencer, chaque programme Python doit avoir un point d’entrée, un endroit où le programme commence son exécution. Lorsqu’un script Python est exécuté directement, par exemple depuis la console, le point d’entrée est souvent signalé par le bloc if __name__ == "__main__"
.
Un programme lancé à partir de la console peut avoir plusieurs points d’entrée. Par exemple, dans une solution Python, il est courant d’avoir un point d’entrée spécifique à chacune des classes métiers afin de créer un test ou un exemple démontrant son fonctionnement. Ces points d’entrée permettent de tester chaque classe ou module individuellement, et peuvent être très utiles pour le débogage et le développement.
Dans l’exemple ci-dessous (ma_classe.py), si le script est exécuté directement à partir de la console, le code dans le bloc if __name__ == "__main__"
sera exécuté. Cependant, si ce script est importé en tant que module dans un autre script, le code ne sera pas exécuté.
# ma_classe.py class MaClasse: def ma_methode_1(self): return "Bonjour tous le monde!" def ma_methode_2(self): return "Hello World!" if __name__ == "__main__": mon_objet = MaClasse() print(mon_objet.ma_methode_1())
# main.py from ma_classe import MaClasse if __name__ == "__main__": mon_objet = MaClasse() print(mon_objet.ma_methode_2())
Si vous exécutez le fichier ma_classe.py directement à partir de la console avec la commande python ma_classe.py
ou grâce à l’option « Run this file » dans PyCharm, le message «Bonjour tous le monde!»
s’affiche. C’est parce que le script est exécuté directement, le code dans le bloc if __name__ == "__main__"
: est exécuté.
En revanche, si vous exécutez le fichier main.py, vous verrez « Hello World!
» s’afficher. Dans ce cas, le message provient du script main.py, et non de ma_classe.py, car ma_classe.py est importé comme un module. Sans ce bloc, l’exécution du script main.py aurait entraîné l’affichage du message «Bonjour tous le monde
», puis le “Hello World!”. Cela est dû au fait que le code ma_methode_1() dans ma_classe.py serait exécutés à l’importation du module, en plus du code ma_methode_2() lui appelé dans le dans main.py.
Le parseur d’argument
Les applications basées sur des arguments, également connues sous le nom d’applications en ligne de commande, tirent parti de l’interface de ligne de commande pour interagir avec l’utilisateur. Le module Python argparse
est l’outil le plus couramment utilisé pour la gestion des arguments en ligne de commande. Il permet de définir les arguments attendus, de spécifier comment ils doivent être interprétés et de générer automatiquement des messages d’aide et d’erreur appropriés.
Un argument nommé est précédé d’un double tiret (–). Par exemple, dans la commande python myscript.py --input file.txt
, où nous avons input
comme argument nommé. Dans l’exemple ci-dessous, nous avons défini deux arguments nommés, –input et –output, et nous avons spécifié un message d’aide pour chaque argument. Ce message sera affiché si l’utilisateur demande de l’aide ou utilise mal la commande.
import argparse # Création d'un nouvel objet ArgumentParser parser = argparse.ArgumentParser(description="Cette application fait quelque chose d'incroyable.") # Ajout d'arguments parser.add_argument("--input", help="chemin du fichier d'entrée") parser.add_argument("--output", help="chemin du fichier de sortie") # Analyse des arguments args = parser.parse_args() # Utilisation des arguments print("Fichier d'entrée :", args.input) print("Fichier de sortie :", args.output)
Il est également possible de définir une valeur par défaut pour un argument. Si l’utilisateur ne fournit pas une valeur pour cet argument lors de l’exécution de la commande, la valeur par défaut sera utilisée. Dans l’exemple ci-dessous l’argument –verbosity a une valeur par défaut de « info ». Si l’utilisateur ne spécifie pas autrement, la verbosité de la sortie sera donc réglée sur « info ». Les options valides pour cet argument sont égalements limitées aux choix spécifiés.
parser.add_argument("--verbosity", help="augmente la verbosité de sortie", default="info", choices=["debug", "info", "warning", "error", "critical"])
Dans une application basée sur des arguments, la gestion correcte des arguments est évidente. Cependant, ne négligez pas la structure générale de l’application. En effet, après avoir analysé les arguments avec argparse, il faut savoir comment utiliser ces arguments pour contrôler le comportement du programme. Une approche courante est d’organiser le code en différentes fonctions, et d’utiliser les arguments pour déterminer quelles parties du code doivent être exécutées.
Par exemple, si votre application a différents modes de fonctionnement, chaque mode peut être géré par une fonction distincte, et un argument peut être utilisé pour sélectionner la fonction appropriée. L’utilisation d’une classe permet de regrouper proprement les fonctionnalités liées et de tirer parti des avantages de la programmation orientée objet. Dans l’exemple ci-dessous, une classe MyApplication encapsule toute la logique de notre application. Le constructeur initialise l’objet argparse et configure les arguments attendus. Les méthodes mode1 et mode2 contiennent le code à exécuter pour chaque mode, et la méthode run analyse les arguments et exécute le mode approprié.
import argparse class MyApplication: def __init__(self): self._parser = argparse.ArgumentParser(description="Cette application fait quelque chose d'incroyable.") self._init_arguments() def _init_arguments(self): self._parser.add_argument("--mode", help="Choisir le mode", choices=["1", "2"]) self._parser.add_argument("--input", help="chemin du fichier d'entrée") self._parser.add_argument("--output", help="chemin du fichier de sortie") self._parser.add_argument("--verbosity", help="augmente la verbosité de sortie", default="info", choices=["debug", "info", "warning", "error", "critical"]) def mode1(self): print("Mode 1") def mode2(self): print("Mode 2") def run(self): args = self._parser.parse_args() if args.mode == "1": self.mode1() elif args.mode == "2": self.mode2() else: print("Mode inconnu!") if __name__ == "__main__": app = MyApplication() app.run()
Contrairement aux applications basées sur des menus qui utilisent généralement une boucle principale pour interagir de manière répétée avec l’utilisateur, les applications basées sur des arguments fonctionnent plutôt sur le principe du « one shot ». Autrement dit, elles sont conçues pour être lancées avec un ensemble spécifique d’arguments, exécuter une tâche en fonction de ces arguments, puis se termine. Il n’y a pas de boucle principale dans une application basée sur des arguments. Au lieu de cela, le point d’entrée principal du programme appelle généralement une seule méthode, qui effectue la tâche requise et se termine.
La philosophie derrière ce genre d’application est « faire peu, mais bien ». En d’autres termes, chaque application est conçue pour réaliser une tâche spécifique de manière efficace, sans chercher à tout faire. Cette philosophie s’inspire de la conception des outils Unix, où de nombreux outils de ligne de commande effectuent une tâche spécifique, mais peuvent être combinés de manière puissante.