Nous avons vu, dans le chapitre précédent, le concept et la modélisation des objets dans la programmation. Il est temps à présent d'implémenter les objets sous la forme de code. Python est un langage orienté objet. En effet, l'intégralité des types de données que nous avons manipulés jusqu'alors sont des objets. Il est temps à présent de mettre en œuvre nos connaissances en matière de programmation orientée objet pour simplifier la structure de nos programmes devenus très complexes. Ce nouveau paradigme nous permettra dans le chapitre suivant l'utilisation des interfaces graphiques.
Nous allons regrouper toutes les méthodes, y compris les méthodes spéciales, avec le mot-clef class suivi du nom de la classe et de la classe mère qu'hérite la classe. Si la classe n'hérite d'aucune classe mère, inscrivez object. Voici l'implémentation de la classe Personne que nous avons abordée dans le chapitre précédent.
Voici le code :
class Personne(object): def __init__(self): self.nom = "Dubois" self.prenom = "Paul" self.lieuDeResidence = "Le Havre" def demenager(self, nouveauLieuDeResidence): self.lieuDeResidence = nouveauLieuDeResidence
Détaillons le code ci-dessus :
Chaque classe est identifiée à partir d'un nom que vous donnez. Ce nom est à choisir afin qu'il respecte les consignes suivantes :
On observe l'apparition du mot self au début des variables qui indique que l'on travaille sur l'instance de la classe. Voici un petit tableau présentant la syntaxe des noms de variables dans les classes.
Syntaxe | Visibilité | Portée | |
---|---|---|---|
variable | Privée | La fonction actuelle seulement | |
self.variable | Publique | Toute l'instance de l'objet | |
self.__variable | Privée | Toute l'instance de l'objet |
Il est également possible de protéger une méthode en la faisant précéder du double symbole souligné (__). Cette méthode ne sera accessible uniquement à l'intérieur de la classe.
Une variable privée peut cependant être lue et modifiée via des méthodes spécifiques nommées accesseurs (lecture) et mutateurs (modification). Cela permet d'effectuer des modifications ou des contrôles sur les données avant qu'elles soient retournées ou modifiées. Il est vivement recommandé de procéder ainsi plutôt que d'offrir les variables publiquement. L'exemple suivant permet de mettre en œuvre cette protection des attributs :
class Personne(object): def __init__(self): self.__nom = "Dubois" self.__prenom = "Paul" self.__lieuDeResidence = "Le Havre" def __demenager(self, nouveauLieuDeResidence): self.__lieuDeResidence = nouveauLieuDeResidence def demanderNom(self): return("Je suis " + self.__prenom + " " + self.__nom)
Dans l'exemple ci-dessus, la fonction __demenager est accessible uniquement depuis l'objet, alors que la méthode demanderNom est accessible depuis tout le programme.
Nous avons déjà utilisé des objets. Pour créer une instance d'une classe, nous allons saisir instanceClasse = Classe(). Il est possible de définir les valeurs par défaut des variables de l'instance en les communiquant en argument à la méthode constructeur. Voici un exemple mettant en œuvre l'instanciation d'une classe et le passage d'arguments pour le constructeur :
class Personne(object): def __init__(self, nom, prenom, lieuDeResidence): self.__nom = nom self.__prenom = prenom self.__lieuDeResidence = lieuDeResidence def __demenager(self, nouveauLieuDeResidence): self.__lieuDeResidence = nouveauLieuDeResidence def demanderNom(self): return("Je suis " + self.__prenom + " " + self.__nom) personne1 = Personne("Dupont", "Clara", "Lille") personne2 = Personne("Martin", "Julie", "Béziers") print(personne1.demanderNom()) # Affiche "Je suis Clara Dupont" print(personne2.demanderNom()) # Affiche "Je suis Julie Martin"
Python permet de définir des méthodes spéciales qui permettent de faciliter l'utilisation des objets. Nous allons présenter une méthode déclenchée quand on cherche à convertir notre objet en chaîne de caractères (str() ou print()). Cette méthode doit se nommer __str__. Il est également possible de le faire pour récupérer un entier ou un réel avec respectivement les méthodes __int__ et __float__. En voici un exemple :
class Personne(object): def __init__(self, nom, prenom, lieuDeResidence): self.__nom = nom self.__prenom = prenom self.__lieuDeResidence = lieuDeResidence def __demenager(self, nouveauLieuDeResidence): self.__lieuDeResidence = nouveauLieuDeResidence def __str__(self): return("Je suis " + self.__prenom + " " + self.__nom + " et j'habite à " + self.__lieuDeResidence) personne1 = Personne("Dupont", "Clara", "Lille") personne2 = Personne("Martin", "Julie", "Béziers") print(personne1) # "Je suis Clara Dupont et j'habite à Lille" print(personne2) # "Je suis Julie Martin et j'habite à Béziers"
Il est également possible de définir des méthodes spéciales permettant de comparer les objets avec les opérateurs de comparaison que nous avons vus précédemment (voir ici). On appelle cela la comparaison riche. Voici la liste des méthodes spéciales associées aux opérateurs de comparaison.
Comparateur | Syntaxe | Méthode associée |
---|---|---|
a égal à b | a == b | __eq__ |
a différent de b | a != b | __ne__ |
a supérieur à b | a > b | __gt__ |
a supérieur ou égal à b | a >= b | __ge__ |
a inférieur à b | a < b | __lt__ |
a inférieur ou égal à b | a <= b | __le__ |
Ces méthodes doivent avoir comme argument l'objet avec lequel comparer l'objet actuel. Voici une implémentation de ces comparateurs riches :
class Personne(object): def __init__(self, nom, prenom, age): self.__nom = nom self.__prenom = prenom self.__age = age def getPrenom(self): return(self.__prenom) def getAge(self): return(self.__age) def __lt__(self, autrePersonne): return(self.__age < autrePersonne.getAge()) personne1 = Personne("Dupont", "Clara", 24) personne2 = Personne("Martin", "Julie", 27) if personne1 < personne2: # Utilise personne1.__lt__(personne2) print(personne1.getPrenom() + " est la plus jeune. ") else: print(personne2.getPrenom() + " est la plus jeune. ")
L'héritage se matérialise par la modification de l'argument lors de la définition de la classe. Nous allons reprendre le modèle UML des animaux vus précédemment.
Nous allons implémenter ces classes en utilisant les mécanismes d'héritage :
class Animaux(object): def __init__(self, nom): self.__nom = nom self.__nombrePattes = 0 self.__position = 0 def avancer(self, nombrePas): self.__position += nombrePas return("Je suis à la position " + str(self.__position)) class Chien(Animaux): def __init__(self, nom): Animaux.__init__(self, nom) self.__nombrePattes = 4 self.aboyer() def aboyer(self): print("Wouf !") class Poule(Animaux): def __init__(self, nom): Animaux.__init__(self, nom) self.__nombrePattes = 2 self.caqueter() def caqueter(self): print("Cot !")
Les lignes 11 et 17 permettent de déclencher le constructeur de la classe mère en lui communiquant les arguments nécessaires.
Nous allons écrire un programme permettant à un utilisateur de jouer au jeu du Yahtzee contre des joueurs pilotés par l'ordinateur.
Le Yahtzee est un jeu de société se jouant à l'aide de cinq dés à six faces et où le but est d'obtenir un maximum de points.
Lorsque c'est au tour d'un joueur de jouer, celui-ci doit essayer de réaliser des combinaisons détaillées ci-dessous à l'aide des cinq dés qu'il peut jeter trois fois par tour. Le joueur est libre quant au nombre de dés à jeter sauf pour le premier jet où il doit jeter tous les dés.
À la fin du tour, le joueur doit inscrire le score obtenu dans la grille des scores, même si le joueur doit inscrire un score nul. Chaque ligne de la grille est utilisable qu'une seule fois, il n'est pas possible de remplacer le score inscrit dans une ligne.
Une partie se compose de 13 tours afin de remplir les 13 lignes de la grille des scores. La grille des scores se divise en deux parties, la grille supérieure et la grille inférieure.
Nom | Combinaison | Points obtenus |
---|---|---|
Partie supérieure | ||
As | Peu importe | 1 × le nombre de obtenus |
Deux | Peu importe | 2 × le nombre de obtenus |
Trois | Peu importe | 3 × le nombre de obtenus |
Quatre | Peu importe | 4 × le nombre de obtenus |
Cinq | Peu importe | 5 × le nombre de obtenus |
Six | Peu importe | 6 × le nombre de obtenus |
Prime (si la somme des lignes ci-dessus est ≥ à 63 points) | 35 points | |
Partie inférieure | ||
Brelan | Trois dés identiques | Somme de tous les dés |
Petite suite | Quatre dés consécutifs | 30 points |
Grande suite | Cinq dés consécutifs | 40 points |
Full | Trois dés identiques + deux dés identiques | 25 points |
Carré | Quatre dés identiques | Somme de tous les dés |
Yahtzee | Cinq dés identiques | 50 points |
Chance | Peu importe | Somme de tous les dés |
Nous modéliserons la partie, les joueurs, le plateau de jeu et les dés sous forme d'objets. On fera la distinction entre les joueurs humains et ordinateurs par deux classes partageant la même classe mère.
#!/usr/bin/env python3 from random import randint, choice prenoms = ["Arnaud", "Nina", "Jacques", "Laura", "Théo", "Camille", "Hugo", "Estelle"] partieInferieure = [ {"score":"as" , "nom":"As"}, {"score":"deux" , "nom":"Deux"}, {"score":"trois" , "nom":"Trois"}, {"score":"quatre" , "nom":"Quatre"}, {"score":"cinq" , "nom":"Cinq"}, {"score":"six" , "nom":"Six"}, {"score":"prime" , "nom":"Prime"} ] partieSuperieure = [ {"score":"brelan" , "nom":"Brelan"}, {"score":"petiteSuite" , "nom":"Petite suite"}, {"score":"grandeSuite" , "nom":"Grande suite"}, {"score":"full" , "nom":"Full"}, {"score":"carre" , "nom":"Carré"}, {"score":"yahtzee" , "nom":"Yahtzee"}, {"score":"chance" , "nom":"Chance"} ] class De(object): def __init__(self): self.__valeur = 1 self.melanger() def melanger(self): self.__valeur = randint(1,6) def lire(self): return(self.__valeur) class Plateau(object): def __init__(self): self.__des = [De() for i in range(5)] def lire(self): valeurDes = [] for de in self.__des: valeurDes.append(de.lire()) return(valeurDes) def afficherPlateau(self): print(" - ".join(str(i) for i in self.lire())) def melangerPlateau(self): for de in self.__des: de.melanger() def melangerDe(self, noDe): self.__des[noDe].melanger() def calculScore(self): des = self.lire() scores = {"as":0, "deux":0, "trois":0, "quatre":0, "cinq":0, "six":0, "brelan":0, "petiteSuite":0, "grandeSuite":0, "full":0, "carre":0, "yahtzee":0, "chance":0} # Calcul de la partie supérieure for idx, score in enumerate(["as", "deux", "trois", "quatre", "cinq", "six"]): scores[score] = des.count(idx+1)*(idx+1) # Calcul de la partie inférieure occurences = {valeur:des.count(valeur) for valeur in des} if 5 in occurences.values(): scores["yahtzee"] = 50 if 4 in occurences.values(): scores["carre"] = sum(des) if 3 in occurences.values() and 2 in occurences.values(): scores["full"] = 25 isGrandeSuite = False if (1 in des and 2 in des and 3 in des and 4 in des and 5 in des) or (2 in des and 3 in des and 4 in des and 5 in des and 6 in des): isGrandeSuite = True if isGrandeSuite: scores["grandeSuite"] = 40 isPetiteSuite = False if (1 in des and 2 in des and 3 in des and 4 in des) or (2 in des and 3 in des and 4 in des and 5 in des) or (3 in des and 4 in des and 5 in des and 6 in des): isPetiteSuite = True if isPetiteSuite: scores["petiteSuite"] = 30 if 3 in occurences.values(): scores["brelan"] = sum(des) scores["chance"] = sum(des) return(scores) class Joueur(object): def __init__(self, nom, plateau): self.__nom = nom self.plateau = plateau self.__scores = {"as":-1, "deux":-1, "trois":-1, "quatre":-1, "cinq":-1, "six":-1, "prime":0, "brelan":-1, "petiteSuite":-1, "grandeSuite":-1, "full":-1, "carre":-1, "yahtzee":-1, "chance":-1} def lireNom(self): return(self.__nom) def lireScores(self): return(self.__scores) def calculerTotalGeneral(self): totalGeneral = 0 for score in self.__scores.values(): if score == -1: score = 0 totalGeneral += score return(totalGeneral) def afficherScores(self): # Permet un affichage plus élégant totalPartieInferieure = 0 totalPartieSuperieure = 0 print(" * * * Scores de " + self.__nom + " * * *") print("= Partie inférieure =") for etiquette in partieInferieure: score = self.__scores[etiquette["score"]] if score == -1: score = 0 print(etiquette["nom"] + " : " + str(score)) totalPartieInferieure += score print(" Total partie inférieure : " + str(totalPartieInferieure)) print("= Partie supérieure =") for etiquette in partieSuperieure: score = self.__scores[etiquette["score"]] if score == -1: score = 0 print(etiquette["nom"] + " : " + str(score)) totalPartieSuperieure += score print(" Total partie supérieure : " + str(totalPartieSuperieure)) print(" Total general : " + str(self.calculerTotalGeneral())) def enregistrerScore(self, typeScore, score): self.__scores[typeScore] = score totalPartieInferieure = 0 self.__scores["prime"] = 0 for etiquette in partieInferieure: totalPartieInferieure += self.__scores[etiquette["score"]] if totalPartieInferieure >= 63: self.__scores["prime"] = 35 class JoueurHumain(Joueur): def __init__(self, plateau): nom = input("Entrez votre nom : ") Joueur.__init__(self, nom, plateau) def jouer(self): self.plateau.melangerPlateau() tiragesRestants = 2 while tiragesRestants > 0: print("Votre tirage : ") self.plateau.afficherPlateau() reponse = input("Voulez-vous retirer des dés (" + str(tiragesRestants) + " tirages restants) (O/N) : ").upper() if reponse == "O": for noDe,de in enumerate(self.plateau.lire()): print("Dé no " + str(noDe +1) + " : " + str(de)) desARetirer = [False for i in range(5)] boucler = True while boucler: noDeARetirer = input("Entrez le numéro du dé à retirer (1-5, laisser vide pour terminer) : ") if noDeARetirer == "": boucler = False else: noDeARetirer = int(noDeARetirer)-1 desARetirer[noDeARetirer] = True for noDe, de in enumerate(desARetirer): if de == True: self.plateau.melangerDe(noDe) tiragesRestants -= 1 elif reponse == "N": tiragesRestants = 0 print("Votre tirage final : ") self.plateau.afficherPlateau() scores = self.plateau.calculScore() choixEnregistrementScore = [] for combinaison in partieInferieure + partieSuperieure: if combinaison["score"] != "prime": numeroSelection = " " if self.lireScores()[combinaison["score"]] == -1: choixEnregistrementScore.append(combinaison["score"]) numeroSelection = "[" + str(len(choixEnregistrementScore)) + "] " print(numeroSelection + combinaison["nom"] + " : " + str(scores[combinaison["score"]])) reponse = 0 while reponse < 1 or reponse > len(choixEnregistrementScore): reponse = int(input("Entrez le numéro de la ligne dans laquelle inscrire le score (1-" + str(len(choixEnregistrementScore)) + ") : ")) typeScore = choixEnregistrementScore[reponse-1] self.enregistrerScore(typeScore, scores[typeScore]) class JoueurOrdinateur(Joueur): def __init__(self, plateau): nom = prenoms.pop(randint(0,len(prenoms)-1)) Joueur.__init__(self, nom, plateau) def jouer(self): self.plateau.melangerPlateau() print("Le tirage de " + self.lireNom() + " : ") self.plateau.afficherPlateau() scoresPlateau = self.plateau.calculScore() for typeScore, score in self.lireScores().items(): if score > 0: scoresPlateau.pop(typeScore) maxScore = 0 typeMaxScore = "" for typeScore, score in scoresPlateau.items(): if score > maxScore: maxScore = score typeMaxScore = typeScore self.enregistrerScore(typeMaxScore, maxScore) class Partie(object): def __init__(self): self.__plateau = Plateau() nbjoueurs = 0 while nbjoueurs < 1 or nbjoueurs > 6: nbjoueurs = int(input("Entrez le nombre de joueurs ordinateur (1-6) : ")) self.__joueurs = [JoueurHumain(self.__plateau)] for i in range(nbjoueurs): self.__joueurs.append(JoueurOrdinateur(self.__plateau)) print("Vous jouez avec " + self.__joueurs[-1].lireNom()) self.jouer() def jouer(self): for i in range(13): print("Tour no " + str(i+1)) for joueur in self.__joueurs: print("Au tour de " + joueur.lireNom()) joueur.jouer() joueur.afficherScores() print("") print("Fin du tour") for joueur in self.__joueurs: print(joueur.lireNom() + " : " + str(joueur.calculerTotalGeneral())) print("=========================================") joueurGagnant = "" scoreGagnant = 0 for joueur in self.__joueurs: if joueur.calculerTotalGeneral() > scoreGagnant: scoreGagnant = joueur.calculerTotalGeneral() joueurGagnant = joueur.lireNom() print("Le vainqueur est " + joueurGagnant) print("Merci d'avoir joué !") partie = Partie()