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.

Implémenter une classe

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.

uml-personne-impl

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.

SyntaxeVisibilitéPortée
variablePrivéeLa fonction actuelle seulement
self.variablePubliqueToute l'instance de l'objet
self.__variablePrivéeToute 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.

Utiliser un objet

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"

Les méthodes spéciales

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.

ComparateurSyntaxeMéthode associée
a égal à ba == b__eq__
a différent de ba != b__ne__
a supérieur à ba > b__gt__
a supérieur ou égal à ba >= b__ge__
a inférieur à ba < b__lt__
a inférieur ou égal à ba <= 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

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.

uml-animaux

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.

Exercices

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.

NomCombinaisonPoints obtenus
Partie supérieure
AsPeu importe1 × le nombre de de1 obtenus
DeuxPeu importe2 × le nombre de de2 obtenus
TroisPeu importe3 × le nombre de de3 obtenus
QuatrePeu importe4 × le nombre de de4 obtenus
CinqPeu importe5 × le nombre de de5 obtenus
SixPeu importe6 × le nombre de de6 obtenus
Prime (si la somme des lignes ci-dessus est ≥ à 63 points)35 points
Partie inférieure
BrelanTrois dés identiquesSomme de tous les dés
Petite suiteQuatre dés consécutifs30 points
Grande suiteCinq dés consécutifs40 points
FullTrois dés identiques + deux dés identiques25 points
CarréQuatre dés identiquesSomme de tous les dés
YahtzeeCinq dés identiques50 points
ChancePeu importeSomme 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()