Exercice 1. QCM d’activation
Pour chaque question, une seule réponse est correcte.
Question 1. On considère le code suivant :
class Chien:
def __init__(self, nom, age):
self.nom = nom
self.age = age
rex = Chien("Rex", 5)
Dans ce code, rex est :
- A) une classe
- B) un attribut
- C) une instance (objet)
- D) une méthode
Correction
Réponse C. rex est une instance (un objet) de la classe Chien, créé par l’appel du constructeur Chien("Rex", 5).
- A est faux :
Chienest la classe, pasrex; - B est faux :
nometagesont des attributs ; - D est faux : une méthode est une fonction définie dans la classe (comme
__init__).
Question 2. Quel est le rôle du paramètre self dans une méthode ?
- A) C’est un mot-clé obligatoire de Python
- B) Il fait référence à la classe elle-même
- C) Il fait référence à l’objet sur lequel la méthode est appelée
- D) Il contient la valeur de retour de la méthode
Correction
Réponse C. Quand on écrit rex.aboyer(), Python passe automatiquement rex comme valeur de self. C’est une référence vers l’objet courant.
- A est faux :
selfn’est pas un mot-clé, c’est une convention de nommage ; - B est faux :
selfdésigne l’objet (l’instance), pas la classe ; - D est faux : la valeur de retour est donnée par
return.
Question 3. On considère :
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 7)
Que vaut p.x + p.y ?
- A) une erreur car on ne peut pas additionner des attributs
- B)
(3, 7) - C)
10 - D)
"37"
Correction
Réponse C. p.x vaut 3 et p.y vaut 7, donc p.x + p.y vaut 10. La notation pointée permet d’accéder aux attributs d’un objet.
- A est faux : les attributs sont des entiers, l’addition est définie ;
- B est faux : ce serait le cas pour
(p.x, p.y); - D est faux : ce serait le cas si
xetyétaient des chaînes"3"et"7".
Question 4. Que se passe-t-il si on oublie self dans la définition d’une méthode ?
class Voiture:
def __init__(self, marque):
self.marque = marque
def klaxonner(): # self oublié
return "Bip !"
v = Voiture("Renault")
v.klaxonner()
- A) La méthode affiche
"Bip !" - B) Python lève une erreur
TypeError - C) La méthode retourne
None - D) Python ignore le
selfmanquant
Correction
Réponse B. Python passe automatiquement l’objet v comme premier argument lors de l’appel v.klaxonner(), mais la méthode n’attend aucun paramètre. Cela provoque TypeError: klaxonner() takes 0 positional arguments but 1 was given.
- A est faux : l’erreur survient avant l’exécution du corps de la méthode ;
- C est faux : la méthode n’est jamais exécutée ;
- D est faux : Python ne corrige pas cette erreur automatiquement.
Question 5. Quelle méthode spéciale faut-il définir pour que print(obj) affiche une représentation lisible d’un objet ?
- A)
__init__ - B)
__add__ - C)
__repr__ - D)
__eq__
Correction
Réponse C. La méthode __repr__ (ou __str__) est appelée automatiquement par print() pour obtenir une représentation en chaîne de l’objet.
- A est faux :
__init__sert à initialiser l’objet lors de sa création ; - B est faux :
__add__est appelée par l’opérateur+; - D est faux :
__eq__est appelée par l’opérateur==.
Exercice 2. Classe Rectangle (guidé)
On souhaite créer une classe Rectangle pour représenter des rectangles géométriques.
Partie A. Compléter le code suivant :
class Rectangle:
def __init__(self, longueur, largeur):
self.________ = longueur
self.________ = largeur
def perimetre(self):
return ________________________
def surface(self):
return ________________________
def __repr__(self):
return f"Rectangle({self.longueur}, {self.largeur})"
Partie B. Tester la classe en créant un rectangle de longueur 7 et de largeur 5, puis en affichant son périmètre et sa surface.
Partie C (enrichissement). L’héritage permet de créer une classe fille qui hérite des attributs et méthodes d’une classe mère.
Créer une classe Parallelepipede qui hérite de Rectangle et possède en plus un attribut hauteur et une méthode volume(). Le constructeur devra appeler celui de Rectangle avec Rectangle.__init__(self, longueur, largeur).
Correction
Partie A.
class Rectangle:
def __init__(self, longueur, largeur):
self.longueur = longueur
self.largeur = largeur
def perimetre(self):
return 2 * (self.longueur + self.largeur)
def surface(self):
return self.longueur * self.largeur
def __repr__(self):
return f"Rectangle({self.longueur}, {self.largeur})"
On initialise les attributs avec self.longueur et self.largeur. Le périmètre d’un rectangle est $2 \times (L + l)$ et sa surface est $L \times l$.
Partie B.
r = Rectangle(7, 5)
print(r) # Rectangle(7, 5)
print(r.perimetre()) # 24
print(r.surface()) # 35
Partie C.
class Parallelepipede(Rectangle):
def __init__(self, longueur, largeur, hauteur):
Rectangle.__init__(self, longueur, largeur)
self.hauteur = hauteur
def volume(self):
return self.longueur * self.largeur * self.hauteur
p = Parallelepipede(7, 5, 2)
print(p.perimetre()) # 24 (méthode héritée de Rectangle)
print(p.volume()) # 70
La classe Parallelepipede hérite de Rectangle : elle dispose automatiquement des méthodes perimetre() et surface() sans les redéfinir. L’appel Rectangle.__init__(self, longueur, largeur) dans le constructeur permet d’initialiser les attributs hérités.
Remarque : l’héritage n’est pas exigible au baccalauréat. Il est présenté ici à titre d’enrichissement.
Exercice 3. Classe Compte bancaire (guidé)
On souhaite modéliser un compte bancaire par une classe CompteBancaire.
Écrire le constructeur
__init__qui prend en paramètresnumero_compte(entier),nom(chaîne du propriétaire) etsolde(nombre), et les stocke comme attributs d’instance.Écrire une méthode
versement(montant)qui ajoutemontantau solde.Écrire une méthode
retrait(montant)qui retiremontantdu solde.Écrire une méthode
agios()qui applique des agios de 5 % : le solde est multiplié par 0.95.Écrire une méthode
__repr__qui retourne une chaîne de la forme"Compte n°1234 (Marcel) : 300 euros".Tester la classe avec le scénario suivant : créer un compte n°1234 pour Marcel avec un solde de 300 euros, effectuer un retrait de 500 euros, un versement de 2000 euros, puis appliquer les agios. Afficher le compte après chaque opération.
Correction
class CompteBancaire:
def __init__(self, numero_compte, nom, solde):
self.numero = numero_compte
self.nom = nom
self.solde = solde
def versement(self, montant):
self.solde += montant
def retrait(self, montant):
self.solde -= montant
def agios(self):
self.solde = self.solde * 0.95
def __repr__(self):
return f"Compte n°{self.numero} ({self.nom}) : {self.solde} euros"
On teste le scénario :
c = CompteBancaire(1234, "Marcel", 300)
print(c) # Compte n°1234 (Marcel) : 300 euros
c.retrait(500)
print(c) # Compte n°1234 (Marcel) : -200 euros
c.versement(2000)
print(c) # Compte n°1234 (Marcel) : 1800 euros
c.agios()
print(c) # Compte n°1234 (Marcel) : 1710.0 euros
Chaque méthode modifie l’attribut self.solde de l’objet. La méthode __repr__ est appelée automatiquement par print(), ce qui permet un affichage lisible sans écrire de méthode afficher() dédiée.
Remarquons que le solde peut devenir négatif (retrait de 500 sur un solde de 300). On pourrait améliorer la méthode retrait en vérifiant que le montant ne dépasse pas le solde, mais ce n’est pas demandé ici.
Exercice 4. Classe Cercle
On souhaite modéliser un cercle $C(O, r)$ de centre $O(a, b)$ et de rayon $r$.
Définir une classe
Cercleavec un constructeur prenant en paramètres les coordonnéesa,bdu centre et le rayonr.Écrire une méthode
surface()qui retourne la surface du cercle ($\pi r^2$). On importerapidepuis le modulemath.Écrire une méthode
perimetre()qui retourne le périmètre du cercle ($2\pi r$).Écrire une méthode
contient(x, y)qui retourneTruesi le point $A(x, y)$ est situé à l’intérieur du cercle (c’est-à-dire si $\sqrt{(x - a)^2 + (y - b)^2} \leqslant r$), etFalsesinon.Tester avec le cercle $C$ de centre $(1, 2)$ et de rayon $3$. Le point $(2, 3)$ est-il à l’intérieur ? Et le point $(5, 5)$ ?
Correction
from math import pi, sqrt
class Cercle:
def __init__(self, a, b, r):
self.a = a
self.b = b
self.r = r
def surface(self):
return pi * self.r ** 2
def perimetre(self):
return 2 * pi * self.r
def contient(self, x, y):
distance = sqrt((x - self.a) ** 2 + (y - self.b) ** 2)
return distance <= self.r
def __repr__(self):
return f"Cercle(centre=({self.a}, {self.b}), rayon={self.r})"
Test :
c = Cercle(1, 2, 3)
print(c.surface()) # 28.274333882308138
print(c.perimetre()) # 18.84955592153876
print(c.contient(2, 3)) # True
print(c.contient(5, 5)) # False
Pour le point $(2, 3)$ : $\sqrt{(2-1)^2 + (3-2)^2} = \sqrt{1 + 1} = \sqrt{2} \approx 1.41$, ce qui est inférieur à $3$, donc le point est à l’intérieur.
Pour le point $(5, 5)$ : $\sqrt{(5-1)^2 + (5-2)^2} = \sqrt{16 + 9} = \sqrt{25} = 5$, ce qui est supérieur à $3$, donc le point est à l’extérieur.
Exercice 5. Classe Chronomètre
On souhaite créer une classe Chronometre représentant un temps en heures, minutes et secondes.
Écrire le constructeur prenant en paramètres
h,mets(avec des valeurs par défaut à 0).Écrire la méthode
__repr__qui retourne le temps au format"02h15m30s"(chaque valeur sur deux chiffres).Aide : l’expression
f"{valeur:02d}"formate un entier sur deux chiffres avec un zéro initial si nécessaire.Écrire une méthode
en_secondes()qui retourne le temps total converti en secondes.Écrire une méthode
avancer(s)qui ajoutessecondes au chronomètre. Le chronomètre devra gérer les dépassements (60 secondes = 1 minute, 60 minutes = 1 heure).Aide : on peut convertir le temps total en secondes, ajouter
s, puis reconvertir avec des divisions entières//et des modulos%.Écrire la méthode
__add__pour pouvoir additionner deux chronomètres avec l’opérateur+.Tester : créer un chronomètre à
1h45m50s, avancer de 75 secondes, puis additionner avec un chronomètre à0h30m20s.
Correction
class Chronometre:
def __init__(self, h=0, m=0, s=0):
self.h = h
self.m = m
self.s = s
def __repr__(self):
return f"{self.h:02d}h{self.m:02d}m{self.s:02d}s"
def en_secondes(self):
return self.h * 3600 + self.m * 60 + self.s
def avancer(self, s):
total = self.en_secondes() + s
self.h = total // 3600
self.m = (total % 3600) // 60
self.s = total % 60
def __add__(self, other):
total = self.en_secondes() + other.en_secondes()
h = total // 3600
m = (total % 3600) // 60
s = total % 60
return Chronometre(h, m, s)
Test :
c1 = Chronometre(1, 45, 50)
print(c1) # 01h45m50s
c1.avancer(75)
print(c1) # 01h47m05s
Vérification : $1 \times 3600 + 45 \times 60 + 50 = 6350$ secondes. On ajoute 75 : $6350 + 75 = 6425$ secondes. $6425 \div 3600 = 1$ heure (reste $2825$). $2825 \div 60 = 47$ minutes (reste $5$). Résultat : 01h47m05s.
c2 = Chronometre(0, 30, 20)
c3 = c1 + c2
print(c3) # 02h17m25s
Vérification : $6425 + 1820 = 8245$ secondes. $8245 \div 3600 = 2$ heures (reste $1045$). $1045 \div 60 = 17$ minutes (reste $25$). Résultat : 02h17m25s.
La méthode __add__ crée et retourne un nouvel objet Chronometre sans modifier les objets existants, contrairement à avancer qui modifie l’objet sur lequel elle est appelée. Cette distinction entre méthode mutatrice et méthode qui retourne un nouvel objet est importante en POO.
Exercice 6. Classe myString (synthèse)
Les chaînes de caractères Python (str) sont immuables : on ne peut pas les modifier après création. On souhaite créer une classe myString qui encapsule une chaîne et lui ajoute des méthodes de modification similaires à celles des listes.
Écrire le constructeur qui prend une chaîne en paramètre et la stocke dans un attribut
s.Écrire la méthode
__repr__qui retourne la chaîne stockée entourée de guillemets simples, par exemple'hello'.Écrire une méthode
append(texte)qui concatènetexteà la fin de la chaîne stockée. La méthode ne retourne rien (elle modifie l’objet).Écrire une méthode
pop(i)qui supprime le caractère à l’indiceiet renvoie ce caractère (commelist.pop).Aide : on peut reconstruire la chaîne par concaténation de la partie avant l’indice et de la partie après l’indice.
Écrire une méthode
__len__pour quelen(obj)retourne la longueur de la chaîne stockée.Écrire une méthode
__eq__pour pouvoir comparer deux objetsmyStringavec==.Tester :
s1 = myString("hello")
s1.append(" world")
print(s1) # 'hello world'
print(len(s1)) # 11
s1.pop(5)
print(s1) # 'helloworld'
s2 = myString("helloworld")
print(s1 == s2) # True
Correction
class myString:
def __init__(self, s):
self.s = s
def __repr__(self):
return f"'{self.s}'"
def append(self, texte):
self.s = self.s + texte
def pop(self, i):
caractere = self.s[i]
self.s = self.s[:i] + self.s[i + 1:]
return caractere
def __len__(self):
return len(self.s)
def __eq__(self, other):
if isinstance(other, myString):
return self.s == other.s
return False
Test :
s1 = myString("hello")
s1.append(" world")
print(s1) # 'hello world'
print(len(s1)) # 11
L’appel s1.append(" world") modifie l’attribut self.s en le remplaçant par "hello" + " world", soit "hello world".
c = s1.pop(5)
print(c) # ' ' (le caractère à l'indice 5 est l'espace)
print(s1) # 'helloworld'
La méthode pop extrait le caractère à l’indice 5 (l’espace), puis reconstruit la chaîne sans ce caractère : "hello" + "world" = "helloworld".
s2 = myString("helloworld")
print(s1 == s2) # True
La méthode __eq__ vérifie d’abord que other est bien une instance de myString (avec isinstance), puis compare les chaînes internes. Cela évite les erreurs si on compare un myString avec un objet d’un autre type.
Cet exercice illustre le principe d’encapsulation : on enrichit le comportement d’un type existant en l’enveloppant dans une classe avec de nouvelles méthodes, tout en gardant la donnée interne (self.s) comme seule source de vérité.