Dans cet article, vous allez construire une application de tables de multiplication, en utilisant la puissance de la programmation orientée objet (POO) en Python
Vous allez pratiquer les principaux concepts de la POO, et comment les utiliser dans une application entièrement fonctionnelle
Python est un langage de programmation multiparadigme, ce qui signifie qu’en tant que développeurs, nous pouvons choisir la meilleure option pour chaque situation et chaque problème. Lorsque nous parlons de programmation orientée objet, nous faisons référence à l’un des paradigmes les plus utilisés pour construire des applications évolutives, au cours des dernières décennies
Les bases de la POO
Nous allons jeter un coup d’œil rapide au concept le plus important de la P.O.O. en Python, les classes
Une classe est un modèle dans lequel nous définissons la structure et le comportement des objets. Ce modèle nous permet de créer des instances, qui ne sont rien d’autre que des objets individuels fabriqués suivant la composition de la classe
Une classe de livre simple, avec les attributs de titre et de couleur, serait définie comme suit
class Book :
def __init__(self, title, color) :
self.title = title
self.color = color
Si nous voulons créer des instances de la classe book, nous devons appeler la classe et lui passer des arguments
# Objets d'instance de la classe Book
blue_book = Book("The blue kid", "Blue")
green_book = Book("The frog story", "Green")
Une bonne représentation de notre programme actuel serait la suivante
Ce qui est génial, c’est que lorsque nous vérifions le type des instances blue_book et green_book , nous obtenons “Book”
# Impression du type des livres
print(type(blue_book))
#
<classe '__main__.Book'>
print(type(green_book))
#
<classe '__main__.Book'>
Après avoir clarifié ces concepts, nous pouvons commencer à construire le projet 😃
Déclaration de projet
En tant que développeurs/programmeurs, la plupart du temps n’est pas consacré à l’écriture de code, selon thenewstack nous ne passons qu’un tiers de notre temps à écrire ou à refactoriser du code
Nous passons les deux autres tiers à lire le code des autres et à analyser le problème sur lequel nous travaillons
Ainsi, pour ce projet, je vais générer un énoncé de problème et nous allons analyser comment créer notre application à partir de cet énoncé. Ainsi, nous réalisons le processus complet, depuis la réflexion sur la solution jusqu’à sa mise en œuvre avec du code
Un enseignant du primaire veut un jeu pour tester les compétences en multiplication des élèves de 8 à 10 ans.
Le jeu doit avoir un système de vies et de points, où l’élève commence avec 3 vies et doit atteindre un certain nombre de points pour gagner. Le programme doit afficher un message “perdant” si l’élève épuise toutes ses vies.
Le jeu doit avoir deux modes, les multiplications aléatoires et les multiplications en tableau.
Le premier doit donner à l’élève une multiplication aléatoire de 1 à 10, et il doit répondre correctement pour gagner un point. Si ce n’est pas le cas, l’élève perd un point et le jeu continue. L’élève ne gagne que lorsqu’il atteint 5 points.
Le deuxième mode doit afficher une table de multiplication de 1 à 10, où l’élève doit saisir le résultat de la multiplication en question. Si l’élève échoue trois fois, il perd, mais s’il réussit deux tables, le jeu se termine
Je sais que les exigences sont peut-être un peu plus grandes, mais je vous promets que nous les résoudrons dans cet article 😁
Diviser pour mieux régner
La compétence la plus importante en programmation est la résolution de problèmes. En effet, vous devez avoir un plan avant de commencer à pirater le code
Je suggère toujours de prendre le plus gros problème et de le diviser en plus petits qui peuvent être à la fois faciles et efficaces à résoudre
Ainsi, si vous devez créer un jeu, commencez par le diviser en ses parties les plus importantes. Ces sous-problèmes seront beaucoup plus faciles à résoudre
Ce n’est qu’à ce moment-là que vous saurez clairement comment exécuter et intégrer le tout dans le code
Réalisons donc un graphique de l’aspect du jeu
Ce graphique établit les relations entre les objets de notre application. Comme vous pouvez le voir, les deux objets principaux sont la multiplication aléatoire et la multiplication par tableau. La seule chose qu’ils partagent, ce sont les attributs Points et Vies
Ayant toutes ces informations en tête, entrons dans le code
Création de la classe de jeu Parent
Lorsque nous travaillons avec la programmation orientée objet, nous cherchons le moyen le plus propre d’éviter la répétition du code. C’est ce qu’on appelle le DRY (don’t repeat yourself)
Note : Cet objectif n’est pas lié à l’écriture du moins de lignes de code possible (la qualité du code ne doit pas être mesurée par cet aspect) mais à l’abstraction de la logique la plus utilisée
Selon l’idée précédente, la classe mère de notre application doit établir la structure et le comportement désiré des deux autres classes
Voyons comment cela peut se faire
class BaseGame :
# Longueur à laquelle le message est centré
message_lenght = 60
description = ""
def __init__(self, points_to_win, n_lives=3) :
"""Classe de jeu de base
Args :
points_to_win (int) : les points dont le jeu aura besoin pour être terminé
n_lives (int) : Le nombre de vies dont dispose l'élève. La valeur par défaut est 3.
"""
self.points_to_win = points_to_win
self.points = 0
self.lives = n_lives
def get_numeric_input(self, message="") :
while True :
# Obtenir l'entrée de l'utilisateur
user_input = input(message)
# Si l'entrée est numérique, la retourner
# Si elle ne l'est pas, imprimer un message et répéter
if user_input.isnumeric() :
return int(user_input)
else :
print("L'entrée doit être un nombre")
continue
def print_welcome_message(self) :
print("PYTHON MULTIPLICATION GAME".center(self.message_lenght))
def print_lose_message(self) :
print("DÉSOLÉ VOUS AVEZ PERDU TOUTES VIVRES".center(self.message_lenght))
def print_win_message(self) :
print(f "FÉLICITATIONS VOUS AVEZ ATTEINT {self.points}".center(self.message_lenght))
def print_current_lives(self) :
print(f "Actuellement vous avez {self.vies} vies")
def print_current_score(self) :
print(f"\NVotre score est de {self.points}")
def print_description(self) :
print("\N" self.description.center(self.message_lenght) "\N")
# Méthode d'exécution de base
def run(self) :
self.print_welcome_message()
self.print_description()
Wow, cela semble être une classe assez énorme. Laissez-moi l’expliquer en profondeur
Tout d’abord, il faut comprendre les attributs de la classe et le constructeur
Fondamentalement, les attributs de classe sont des variables créées à l’intérieur de la classe, mais en dehors du constructeur ou de toute autre méthode
Les attributs d’instance, quant à eux, sont des variables créées uniquement dans le constructeur
La principale différence entre ces deux types d’attributs est leur portée, c’est-à-dire que les attributs de classe sont accessibles à la fois à partir d’un objet d’instance et de la classe. En revanche, les attributs d’instance ne sont accessibles qu’à partir d’un objet d’instance
game = BaseGame(5)
#
Accès à l'attr de classe longueur du message du jeu depuis la classe
print(game.message_lenght) # 60
#
Accès à l'attr de classe longueur du message depuis la classe
print(BaseGame.message_lenght) # 60
#
Accès à l'attr d'instance points depuis l'instance
print(game.points) # 0
#
Accès à l'attribut d'instance points depuis la classe
print(BaseGame.points) # Erreur d'attrib
ut
Un autre article peut approfondir ce sujet. Restez en contact pour le lire
La fonctionget_numeric_input
est utilisée pour empêcher l’utilisateur de fournir des données qui ne sont pas numériques. Comme vous pouvez le remarquer, cette méthode est conçue pour interroger l’utilisateur jusqu’à ce qu’elle obtienne une entrée numérique. Nous l’utiliserons plus tard dans les classes d’enfants
Les méthodes print nous permettent d’éviter d’imprimer la même chose à chaque fois qu’un événement se produit dans le jeu
Enfin, la méthode run n’est qu’une enveloppe que les classes Random multiplication et Table multiplication utiliseront pour interagir avec l’utilisateur et rendre le tout fonctionnel
Création des classes de l’enfant
Une fois que nous avons créé la classe mère, qui établit la structure et certaines des fonctionnalités de notre application, il est temps de construire les classes du mode de jeu proprement dit, en utilisant la puissance de l’héritage
Classe de multiplication aléatoire
Cette classe exécutera le “premier mode” de notre jeu. Elle va utiliser le module random bien sûr, qui va nous donner la possibilité de demander à l’utilisateur des opérations aléatoires de 1 à 10. Voici un excellent article sur le module random (et d’autres modules importants) 😉
import random # Module pour les opérations aléatoires
class RandomMultiplication(BaseGame) :
description = "Dans ce jeu, vous devez répondre correctement à la multiplication aléatoire : vous gagnez si vous atteignez 5 points, ou vous perdez si vous perdez toutes vos vies"
def __init__(self) :
# Le nombre de points nécessaires pour gagner est de 5
# Passez l'argument 5 "points_to_win"
super().__init__(5)
def get_random_numbers(self) :
first_number = random.randint(1, 10)
second_number = random.randint(1, 10)
return first_number, second_number
def run(self) :
# Appelez la classe supérieure pour imprimer les messages de bienvenue
super().run()
while self.lives > 0 and self.points_to_win > self.points :
# Obtient deux nombres aléatoires
number1, number2 = self.get_random_numbers()
operation = f"{number1} x {number2} : "
# Demande à l'utilisateur de répondre à cette opération
# Prévient les erreurs de valeur
user_answer = self.get_numeric_input(message=operation)
if user_answer == number1 * number2 :
print("\nVotre réponse est correcte\n")
# Ajoute un point
self.points = 1
else :
print("\nSorry, your answer is incorrect\n")
# Substracts a live
self.lives -= 1
self.print_current_score()
self.print_current_lives()
# N'est exécuté que lorsque le jeu est terminé
# Et qu'aucune des conditions n'est vraie
else :
# Imprime le message final
if self.points >= self.points_to_win :
self.print_win_message()
else :
self.print_lose_message(
)
Voici une autre classe massive 😅. Mais comme je l’ai déjà dit, ce n’est pas le nombre de lignes que cela prend, c’est à quel point c’est lisible et efficace. Et la meilleure chose à propos de Python est qu’il permet aux développeurs de faire du code propre et lisible comme s’ils parlaient un anglais normal
Cette classe a une particularité qui peut vous dérouter, mais je vais l’expliquer aussi simplement que possible
# Classe mère
def __init__(self, points_to_win, n_lives=3) :
“…
# Classe enfant
def __init__(self) :
# Le nombre de points nécessaires pour gagner est de 5
# Passez l'argument 5 "points_to_win"
super().__init__(5
)
Le constructeur de la classe enfant appelle la fonction super qui, en même temps, fait référence à la classe mère (BaseGame). Cela revient à dire à Python
Remplissez l’attribut “points_to_win” de la classe mère avec 5 !
Il n’est pas nécessaire de mettre self dans la partie super().__init__()
simplement parce que nous appelons super à l’intérieur du constructeur et que cela serait redondant
Nous utilisons également la fonction super dans la méthode run, et nous allons voir ce qui se passe dans ce morceau de code
#
Méthode d'exécution de base
Méthode parentale
def run(self) :
self.print_welcome_message()
self.print_description()
def run(self) :
# Appelez la classe supérieure pour imprimer les messages de bienvenue
super().run()
....
Comme vous pouvez le remarquer, la méthode run de la classe mère imprime les messages de bienvenue et de description. Mais c’est une bonne idée de conserver cette fonctionnalité et d’en ajouter d’autres dans les classes enfants. En conséquence, nous utilisons super pour exécuter tout le code de la méthode parent avant d’exécuter le morceau suivant
L’autre partie de la fonction run est assez simple. Elle demande à l’utilisateur un nombre avec le message de l’opération à laquelle il doit répondre. Ensuite, le résultat est comparé à la multiplication réelle et, s’ils sont égaux, ajoute un point, s’ils ne le sont pas, enlève 1 vie
Il convient de préciser que nous utilisons des boucles while-else. Cela dépasse le cadre de cet article mais j’en publierai un dans quelques jours
Enfin, get_random_numbers, utilise la fonction random.randint, qui renvoie un entier aléatoire dans l’intervalle spécifié. Elle renvoie ensuite un tuple de deux entiers aléatoires
Classe de multiplication aléatoire
Le “second mode” doit afficher le jeu sous la forme d’une table de multiplication, et s’assurer que l’utilisateur répond correctement à au moins deux tables
Pour ce faire, nous utiliserons à nouveau le pouvoir de super et modifierons l’attribut de la classe parente points_to_win à 2
class TableMultiplication(BaseGame) :
description = "Dans ce jeu, vous devez résoudre correctement la table de multiplication complète. Vous gagnez si vous résolvez 2 tables"
def __init__(self) :
# Doit résoudre 2 tables pour gagner
super().__init__(2)
def run(self) :
# Affiche les messages de bienvenue
super().run()
while self.lives > 0 and self.points_to_win > self.points :
# Obtient deux nombres aléatoires
number = random.randint(1, 10)
for i in range(1, 11) :
if self.lives <= 0 :
# S'assure que le jeu ne peut pas continuer
# si l'utilisateur épuise ses vies
self.points = 0
break
operation = f"{number} x {i} : "
user_answer = self.get_numeric_input(message=operation)
if user_answer == number * i :
print("Great ! Your answer is correct")
else :
print("Sorry your answer isn't correct")
self.lives -= 1
self.points = 1
# N'est exécuté que lorsque le jeu est terminé
# Et qu'aucune des conditions n'est vraie
else :
# Imprime le message final
if self.points >= self.points_to_win :
self.print_win_message()
else :
self.print_lose_message()
Comme vous pouvez le constater, nous ne modifions que la méthode run de cette classe. C’est la magie de l’héritage, nous écrivons une fois la logique que nous utilisons à plusieurs endroits, et nous l’oublions 😅
Dans la méthode run, nous utilisons une boucle for pour obtenir les nombres de 1 à 10 et nous construisons l’opération qui est montrée à l’utilisateur
Une fois de plus, si les vies sont épuisées ou si les points nécessaires pour gagner sont atteints, la boucle while s’interrompt et le message de victoire ou de défaite s’affiche
OUI, nous avons créé les deux modes de jeu, mais jusqu’à présent, si nous exécutons le programme, rien ne se passe
Finalisons donc le programme en implémentant le choix du mode et en instanciant les classes qui dépendent de ce choix
Implémentation du choix
L’utilisateur pourra choisir le mode de jeu qu’il souhaite utiliser. Voyons donc comment l’implémenter
if __name__ == "__main__" :
print("Select Game mode")
choice = input("[1],[2]: ")
if choice == "1" :
game = RandomMultiplication()
elif choice == "2" :
game = TableMultiplication()
else :
print("Please, select a valid game mode")
exit()
game.run()
Tout d’abord, nous demandons à l’utilisateur de choisir entre les modes 1 ou 2. Si l’entrée n’est pas valide, le script s’arrête. Si l’utilisateur choisit le premier mode, le programme exécutera le mode de jeu Multiplication aléatoire , et s’il choisit le second, le mode Multiplication de table sera exécuté
Voici à quoi cela ressemblerait
Conclusion
Félicitations, vous venez de construire une application Python avec la programmation orientée objet
Tout le code est disponible dans le dépôt Github
Dans cet article, vous avez appris à
- Utiliser les constructeurs de classes Python
- Créer une application fonctionnelle avec la POO
- Utiliser la fonction super dans les classes Python
- Appliquer les concepts de base de l’héritage
- Implémenter les attributs de classe et d’instance
Bon codage 👨💻
Ensuite, explorez quelques-uns des meilleurs IDE Python pour améliorer votre productivité.