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 :
Réponse C. rex est une instance (un objet) de la classe Chien, créé par l’appel du constructeur Chien("Rex", 5).
Chien est la classe, pas rex ;nom et age sont des attributs ;__init__).Question 2. Quel est le rôle du paramètre self dans une méthode ?
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.
self n’est pas un mot-clé, c’est une convention de nommage ;self désigne l’objet (l’instance), pas la classe ;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 ?
(3, 7)10"37"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.
(p.x, p.y) ;x et y é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()
"Bip !"TypeErrorNoneself manquantRé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.
Question 5. Quelle méthode spéciale faut-il définir pour que print(obj) affiche une représentation lisible d’un objet ?
__init____add____repr____eq__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.
__init__ sert à initialiser l’objet lors de sa création ;__add__ est appelée par l’opérateur + ;__eq__ est appelée par l’opérateur ==.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})"
Les attributs portent le nom des paramètres du constructeur, précédé de self.. Dans une méthode, on les utilise de la même façon : le périmètre vaut 2 × (longueur + largeur) et la surface longueur × 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.
Créer l’objet avec Rectangle(7, 5) et le ranger dans une variable, puis appeler ses méthodes avec la notation pointée, par exemple r.perimetre().
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).
La classe fille se déclare avec class Parallelepipede(Rectangle):. Son constructeur reçoit les trois dimensions, appelle Rectangle.__init__ pour la longueur et la largeur, puis crée self.hauteur. Le volume vaut longueur × largeur × hauteur.
class Parallelepipede(Rectangle):
def __init__(self, longueur, largeur, hauteur):
Rectangle.__init__(self, ..., ...)
self.hauteur = ...
def volume(self):
return ...
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.
On souhaite modéliser un compte bancaire par une classe CompteBancaire.
Écrire le constructeur __init__ qui prend en paramètres numero_compte (entier), nom (chaîne du propriétaire) et solde (nombre), et les stocke comme attributs d’instance.
Un attribut par paramètre, créé avec self. : par exemple self.solde = solde.
Écrire une méthode versement(montant) qui ajoute montant au solde.
La méthode modifie l’attribut du solde : self.solde = self.solde + montant. Elle ne renvoie rien.
Écrire une méthode retrait(montant) qui retire montant du solde.
Même principe que versement, avec une soustraction.
Écrire une méthode agios() qui applique des agios de 5 % : le solde est multiplié par 0.95.
Multiplier self.solde par 0.95 et ranger le résultat dans self.solde.
Écrire une méthode __repr__ qui retourne une chaîne de la forme "Compte n°1234 (Marcel) : 300 euros".
__repr__ renvoie une f-string construite avec les attributs du compte, sur le modèle f"Compte n°{...} ({...}) : {...} 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.
Créer le compte avec CompteBancaire(1234, "Marcel", 300), puis appeler chaque méthode et afficher le compte avec print après chaque opération : print utilise __repr__.
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.
On souhaite modéliser un cercle $C(O, r)$ de centre $O(a, b)$ et de rayon $r$.
Définir une classe Cercle avec un constructeur prenant en paramètres les coordonnées a, b du centre et le rayon r.
Trois attributs, un par paramètre : self.a, self.b et self.r.
Écrire une méthode surface() qui retourne la surface du cercle ($\pi r^2$). On importera pi depuis le module math.
Écrire from math import pi en haut du programme, puis traduire la formule avec l’attribut du rayon, self.r. Le carré s’écrit ** 2.
Écrire une méthode perimetre() qui retourne le périmètre du cercle ($2\pi r$).
Écrire une méthode contient(x, y) qui retourne True si 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$), et False sinon.
Calculer la distance entre le point et le centre avec sqrt (à importer aussi depuis math), puis renvoyer directement le résultat de la comparaison distance <= self.r, qui vaut True ou False.
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)$ ?
Créer c = Cercle(1, 2, 3), puis afficher c.contient(2, 3) et c.contient(5, 5).
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.
On souhaite créer une classe Chronometre représentant un temps en heures, minutes et secondes.
Écrire le constructeur prenant en paramètres h, m et s (avec des valeurs par défaut à 0).
Les valeurs par défaut s’écrivent dans la ligne de définition : def __init__(self, h=0, m=0, s=0):. Ranger ensuite chaque paramètre dans un attribut.
É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.
Une heure vaut 3600 secondes et une minute 60 secondes.
Écrire une méthode avancer(s) qui ajoute s secondes 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 %.
def avancer(self, s):
total = self.en_secondes() + s
self.h = total // ...
self.m = (total % ...) // ...
self.s = total % ...
Écrire la méthode __add__ pour pouvoir additionner deux chronomètres avec l’opérateur +.
__add__(self, other) reçoit le second chronomètre. Additionner les deux temps en secondes, convertir le total en heures, minutes et secondes comme dans avancer, puis construire et renvoyer un nouveau Chronometre : les deux chronomètres de départ ne changent pas.
Tester : créer un chronomètre à 1h45m50s, avancer de 75 secondes, puis additionner avec un chronomètre à 0h30m20s.
c1 = Chronometre(1, 45, 50), puis c1.avancer(75) ; l’expression c1 + Chronometre(0, 30, 20) appelle __add__. Afficher le résultat avec print.
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.
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'.
__repr__ renvoie une chaîne : construire une f-string qui place self.s entre deux guillemets simples.
Écrire une méthode append(texte) qui concatène texte à la fin de la chaîne stockée. La méthode ne retourne rien (elle modifie l’objet).
Concaténer avec + et ranger le résultat dans self.s.
Écrire une méthode pop(i) qui supprime le caractère à l’indice i et renvoie ce caractère (comme list.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.
def pop(self, i):
caractere = self.s[...]
self.s = self.s[:...] + self.s[...:]
return caractere
Écrire une méthode __len__ pour que len(obj) retourne la longueur de la chaîne stockée.
len(obj) appelle obj.__len__() : renvoyer la longueur de la chaîne stockée dans l’attribut.
Écrire une méthode __eq__ pour pouvoir comparer deux objets myString avec ==.
__eq__(self, other) reçoit l’objet comparé : deux myString sont égaux lorsque leurs attributs s sont égaux.
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
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é.