Correction : Exercices POO
Utilisation d'objet ( travail papier-crayon )
Instanciation des objets et appel de leurs méthodes
- Créer la carte Valet de COEUR que l’on nommera c1 :
Le constructeur de la classe attend deux paramètres : le nom et la couleur de la carte; ces valeurs, passées en argument au constructeur, sont les indices dans le tuple couleurs et la liste noms.
c1 = Carte(9, 1) # 9 = indice de 'COEUR' / 1 = indice de 'Valet'Attention, on ne passe pas self en argument ( c'est déjà fait implicitement. )
- Afficher le nom, la valeur et la couleur de c1 :
D'après le principe d'encapsulation des attributs, on ne peut accéder directement à ceux-ci depuis "l'extérieur" de l'objet; il faut pour cela appeler les méthodes
get_nom(),get_couleur()etget_valeur()appelées "accesseurs" ( = getter ) et destinées à ces accès :print(c1.get_nom(), c1.get_valeur(), c1.get_couleur()) - Créer la carte As de PIQUE que l’on nommera c2 :
c2 = Carte(12, 3) - Afficher le nom, la valeur et la couleur de c2 :
print(c2.get_nom(), c2.get_valeur(), c2.get_couleur()) - Modifier le nom de la carte c2 en Roi et afficher le nom, la valeur et la couleur de c2 :
On ne peut non plus modifier directement les attributs depuis "l'extérieur" de l'objet; il faut pour cela appeler la méthode
setNom()appelée "mutateur" ( = setter ) destinée à cette modification :c2.set_nom(11) print(c2.get_nom(), c2.get_valeur(), c2.get_couleur()) - Créer la carte 8 de TREFLE que l’on nommera c3 :
c3 = Carte(6, 2) - Comparer les cartes c1 et c2 puis c1 et c3.
Il faut appeler la méthode
egalite()( par exemple ) à partir d'un objet, et lui passer l'autre objet comme argument.print(c3.egalite(c2)) print(c1.egalite(c3))
Code complet des méthodes
class Carte:
def __init__(self, nom, couleur):
# Affectation de l'attribut nom et de l'attribut couleur
couleurs = ('CARREAU', 'COEUR', 'TREFLE', 'PIQUE')
noms = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'Valet', 'Dame', 'Roi', 'As']
valeurs = {'2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10, 'Valet': 11, 'Dame': 12, 'Roi': 13, 'As': 14}
self.couleur = couleurs[couleur]
self.nom = noms[nom]
self.valeur = valeurs[self.nom]
def set_nom(self, nom):
# Mutateur de l'attribut nom (de la liste noms)
self.nom = nom
def get_nom(self):
# renvoie le nom de la carte (de la liste noms): Accesseur
return self.nom
def get_couleur(self):
# renvoie la couleur de la carte (de la liste couleur): Accesseur
return self.couleur
def get_valeur(self):
# renvoie la valeur de la carte (du dictionnaire valeurs) : Accesseur
return self.valeur
def egalite(self, carte):
''' Renvoie True si les cartes self et carte ont même valeur, False sinon
carte: Objet de type Carte
'''
if self.valeur == carte.get_valeur:
return True
else :
return False
def est_superieure(self, carte):
''' Renvoie True si la valeur de self est supérieure à celle de carte, False sinon
carte: Objet de type Carte
'''
if self.valeur > carte.get_valeur():
return True
else :
return False
def est_inferieure(self, carte):
''' Renvoie True si la valeur de self est inférieure à celle de carte, False sinon
carte: Objet de type Carte
'''
return self.valeur < carte.get_valeur():
Commentaires :
- dans le constructeur ( lignes 3 à 10 ) : on initialise les attributs de la carte ( lignes 9 et 10 ) avec les éléments des listes noms et couleurs, éléments dont les indices respectifs sont donnés par
les arguments nom et couleur.
On peut noter que pour l'attribut valeur (ligne 11 ), la valeur est celle du dictionnaire valeurs dont la clé est l'attribut nom. - le mutateur ( lignes 12 à 14 ) permet de modifier l'attribut nom depuis "l'extérieur" de l'objet, en appelant cette méthode à partir de cet objet.
- les accesseurs ( lignes 16 à 26 ) renvoie simplement la valeur des attributs.
- pour les méthodes de comparaison de deux cartes ( lignes 28 à 53 ), qui fonctionnent toutes de la même façon, on compare les valeurs de l'objet à partir duquel la méthode est appelée ( self ), et de celui qui est passé en
argument à cette méthode ( carte ).
On note que pour ce dernier, on utilise l'accesseur sur le nom au lieu de "consulter" directement son attribut valeur. - on peut également remarquer que, pour une fonction renvoyant un booléen, on peut directement renvoyer le résultat du test, sans besoin de structure
if...else...(ligne 51 )
Des cercles
Création de la classe
La consigne était que la classe n'ait qu'un seul attribut : en effet, un cercle est entièrement défini par son rayon, lui rajouter une surface et un périmètre comme attributs supplémentaires est superflu et donc inutile.
Par contre, rien n’empêche d'utiliser des variables locales dans les méthodes pour le calcul de la surface et du périmètre avant de retourner ces valeurs, même si ce n'est pas indispensable ( on peut bien entendu retourner directement le résultat du calcul ).
from math import pi
class Cercle:
def __init__(self, rayon): # passage du rayon en argument au constructeur
self.rayon = rayon
def surface(self):
return pi*self.rayon**2
def perimetre(self):
return 2*pi*self.rayon
def est_plus_grand(self, c): # c est un argument de type 'Cercle'
return self.rayon > c.rayon # comparaison de l'attribut 'rayon' de self et de l'objet passé en argument et renvoi du résultat
Instanciation des objets
On pouvait créer 5 instances complètement séparées de Cercle, ou alors les regrouper dans une liste comme dans le TP "Champagne !" ; rien n'est imposé !
Par contre, il faut penser à passer la valeur du rayon en argument lors de l'instanciation : le constructeur attend ce paramètre.
c1 = Cercle(5)
c2 = Cercle(21.2)
c3 = Cercle(15)
c4 = Cercle(6)
c5 = Cercle(9.7)
Appel des méthodes surface() et perimetre()
>>> c1.perimetre()
31.41592653589793
>>> c5.surface()
295.5924527762636
Méthode de comparaison
Il faut bien comprendre comment fonctionne la méthode est_plus_grand():
- elle s'appelle depuis un des objets
Cercle, la consigne parle du deuxième : on écrira doncc2.est_plus_grand() - elle doit comparer c2 à c4 : on passe donc ce dernier comme argument à la méthode :
c2.est_plus_grand(c4)
>>> c2.est_plus_grand(c4)
True
Affichage d'un objet
- si on se contente d'un
printsur un objet, ce que l'on affiche en réalité est son adresse en mémoire :c1 = Cercle(18) print(c1) >>> <__main__.Cercle object at 0x7f7196be6e50>Il se passe d'ailleurs la même chose si on utilise
printavec le nom d'une fonction ( ou d'une méthode ) en oubliant les parenthèses : au lieu d'appeler la fonction et d'afficher le résultat qu'elle renvoie, on affiche en fait l'adresse en mémoire de la fonction. - Code de la méthode
__str__:class Cercle: ... def __str__(self): return 'Rayon = ' + str(self.rayon) ...
Des chiens
Classe Chien
from random import shuffle
class Chien :
def __init__(self, nom, aboiement, points_sante = 100):
self.nom = nom
self.points_sante = points_sante
self.aboiement = 'Grrr...' + aboiement
def mordre(self, autre_chien):
autre_chien.points_sante -= 10
def manger(self):
self.points_sante += 20
def grogner(self):
return self.aboiement
def machouiller(self, chaine):
chaine = list(chaine)
shuffle(chaine)
chaine = "".join(chaine)
return chaine
- dans le constructeur ( ligne 6 ) : il y a 3 paramètres ( en plus de self bien entendu...); les paramètres avec des valeurs par défaut doivent toujours être indiqués en dernier.
- attention à la méthode
mordre()(ligne 11 ) : elle prend en paramètre un objet de typeChien, qui correspond ici à la variable autre_chien. - il fallait trouver ( sur le web ! ) la "recette" qui permet de mélanger les caractères d'une chaîne; voila une version qui utilise la fonction
shuffle()du modulerandom, importé au début du script.
Tout cela semble fonctionner...mais respecte-t-on bien tous les concepts de la POO ? En fait, non : on modifie directement l'attribut d'un autre objet dans la méthode mordre() ( ligne 12 ), ce qui ne respecte pas le principe
d'encapsulation des attributs : seul autre_chien devrait pouvoir modifier son attribut points_sante.
Il faudrait donc prévoir dans la classe une méthode destinée à modifier indirectement cet attribut à partir d'un autre objet; une telle méthode est appelée un mutateur ( ou setter en anglais ) :
def modifie_points_sante(self):
self.points_sante -= 10
La méthode mordre() appellerait alors cette méthode, à partir de l'objet autre_chien :
def mordre(self, autre_chien):
autre_chien.modifie_points_sante()
Utilisation de la classe
Instancier quelques chiens ayant des noms et des aboiements différents :
>>> medor = Chien('Medor', 'ouaf') # valeur de points_sante par défaut ( 100 )
>>> kiki = Chien('Kiki', 'wah-wah', 50) # valeur de points_sante = 50
Faire manger un des chiens et vérifier que ses points de santé ont bien augmenté :
>>> medor.manger()
>>> medor.points_sante
120
Faire grogner un chien :
>>> medor.grogner()
'Grrr...ouaf'
Faire mâchouiller à un chien une chaîne de caractères quelconque :
>>> kiki.machouiller('Au pied!')
'!uepdAi '
Faire mordre un chien par un autre et vérifier que ses points de santé ont bien baissé :
>>> medor.mordre(kiki)
>>> kiki.points_sante
40
Une horloge
from time import sleep
class Horloge:
def __init__(self, heure, minutes, secondes):
self.heure = heure
self.minutes = minutes
self.secondes = secondes
def affiche(self):
print(self.heure, ':', self.minutes, ':', self.secondes)
def tic_tac(self):
self.secondes += 1
if self.secondes == 60:
self.secondes = 0
self.minutes += 1
if self.minutes == 60:
self.minutes = 0
self.heure += 1
if self.heure == 24:
self.heure = 0
horloge = Horloge(13,59,55)
while True:
horloge.affiche()
sleep(1)
horloge.tic_tac()
Des boîtes
from random import randint
class Boite:
def __init__(self, longueur, largeur, hauteur):
self.longueur = longueur
self.largeur = largeur
self.hauteur = hauteur
def volume(self):
return self.longueur * self.largeur * self.hauteur
def rentre_dans(self, autre_boite):
return autre_boite.longueur > self.longueur and autre_boite.largeur > self.largeur and autre_boite.hauteur > self.hauteur # on teste si les TROIS dimensions de la deuxième boîte sont plus petites
def tri_selection(boites):
for i in range(len(boites)):
mini = i
for j in range(i+1, len(boites)):
if boites[j].volume() > boites[mini].volume(): # on trie des objets, pas des entiers !
mini = j
if mini != i:
boites[i], boites[mini] = boites[mini], boites[i]
# Création du tableau de 20 objets Boite
boites = []
for i in range(20):
boites.append(Boite(randint(1, 50), randint(1, 50), randint(1, 50)))
# tri de la liste des boîtes
tri_selection(boites)
# Construction de la suite des boîtes gigognes
suite = [boites[0]]
for i in range(1, len(boites)):
if boites[i].rentre_dans(suite[-1]): # si la boite de plus grand volume possible rentre dans la dernière ( = indice -1 dans le tableau ) boîte de la suite
suite.append(boites[i])
# Vérification
for boite in suite:
print("L =", boite.longueur, "l =", boite.largeur, "h =", boite.hauteur)