Exercices#
Programmation orientée objet et paradigmes
Mode d’emploi
-
Niveau : ★ échauffement, ★★ classique (niveau bac), ★★★ défi ; les coups de pouce et les corrigés se déplient sous chaque exercice.
-
▶ À programmer et tester en Python (console ou éditeur en ligne).
-
Réflexe pour toute classe : (1) quels sont les attributs (l’état) ? (2) quelles sont les méthodes (le comportement) ? (3) le premier paramètre d’une méthode est toujours
self. -
Usage de l’IA, indiqué sur chaque exercice : aucune ; en appui (déboguer, reformuler, vérifier ; la réponse finale est écrite et comprise par vous) ; l’exercice s’appuie sur une réponse d’IA. Voir la charte d’usage de l’IA.
Le paradigme fonctionnel#
On commence par le paradigme le plus léger : il n’utilise que des fonctions. Une fonction est pure si son résultat ne dépend que de ses paramètres et qu’elle ne modifie rien d’extérieur (aucun effet de bord).
★ Exercice 1 — Pure ou pas pure ? #
• essentiel
Pour chacune de ces fonctions, dire si elle est pure, et justifier.
Coup de pouce
Pour chaque fonction, se poser deux questions. Son résultat dépend-il d’autre chose que de ses paramètres ? Modifie-t-elle une variable ou un objet qui existe en dehors d’elle (variable globale, liste reçue en paramètre) ?
compteur = 0
def f1(x):
return 2 * x + 1
def f2(x):
global compteur # f2 modifie la variable compteur exterieure
compteur = compteur + 1
return x
def f3(liste):
liste.append(0)
return liste
def f4(a, b):
return a * b
Corrigé
Remarque
Plusieurs écritures sont souvent possibles : on donne une version simple.
-
f1: pure — son résultat ne dépend que dex, aucun effet de bord. -
f2: non pure — elle modifie la variable globalecompteur(effet de bord). -
f3: non pure — elle modifie la liste reçue avecappend(effet de bord). -
f4: pure — résultat fonction des seuls paramètres, aucun effet de bord.
★★ Exercice 2 — Rendre une fonction pure #
On donne une fonction avec effet de bord :
def double_tout(tab):
for i in range(len(tab)):
tab[i] = tab[i] * 2 # modifie tab : effet de bord
▶ Réécrire une fonction double_tout_pur(tab) qui renvoie une nouvelle liste des éléments doublés, sans modifier tab. Vérifier sur un exemple que la liste d’origine est bien intacte.
Coup de pouce
Partir d’une liste vide, la remplir dans une boucle, puis la renvoyer : aucune affectation de la forme tab[i] = ... ne doit apparaître.
>>> t = [1, 2, 3]
>>> double_tout_pur(t)
[2, 4, 6]
>>> t
[1, 2, 3]
Corrigé
On construit une nouvelle liste au lieu de modifier l’ancienne : on parcourt tab et on garde le double de chaque élément.
def double_tout_pur(tab):
return [x * 2 for x in tab]
Autre méthode (sans compréhension, comme le suggère le coup de pouce) : on part d’une liste vide que l’on remplit dans une boucle.
def double_tout_pur(tab):
resultat = []
for x in tab:
resultat.append(x * 2)
return resultat
La liste d’origine t reste bien [1, 2, 3].
★★★ Exercice 3 — Défi — des fonctions qui manipulent des fonctions #
≈ 20 min
En paradigme fonctionnel, une fonction est une valeur comme une autre : on peut la passer en paramètre à une autre fonction, ou la renvoyer. On donne :
def est_pair(n):
return n % 2 == 0
def double(x):
return 2 * x
def plus_un(x):
return x + 1
-
▶ Écrire une fonction pure
appliquer(f, tab)qui renvoie la nouvelle liste desf(x)pourxdanstab. Exemple :appliquer(abs, [-2, 3, -5])renvoie[2, 3, 5], et la liste passée en paramètre est inchangée. -
▶ Écrire une fonction pure
garder(test, tab)qui renvoie la liste des élémentsxdetabpour lesquelstest(x)vautTrue. Exemple :garder(est_pair, [1, 2, 3, 4])renvoie[2, 4]. -
▶ Écrire une fonction
composer(f, g)qui renvoie une fonctionhtelle queh(x)vautf(g(x)). Exemple :composer(double, plus_un)(3)renvoie8. Que renvoiecomposer(plus_un, double)(3)?Coup de pouce
Pour les questions 1 et 2 : construire une nouvelle liste, sans jamais modifier
tab;fs’appelle comme n’importe quelle fonction,f(x). Pour la question 3 : on peut définir une fonction à l’intérieur d’une autre fonction, puis la renvoyer (sans parenthèses : on renvoie la fonction, pas son résultat).Coup de pouce 2 (début de solution)
def composer(f, g):
def h(x):
return ...
return h -
Sans définir de fonction, quelle construction vue en Première donne directement le résultat de
appliquer(abs, [-2, 3, -5])? et celui degarder(est_pair, [1, 2, 3, 4])?
Corrigé
Les paramètres f et test sont des fonctions : on les appelle comme les autres. On construit toujours une nouvelle liste, donc tab n’est jamais modifié : les fonctions sont pures. Une création de liste par compréhension convient parfaitement : on transforme chaque élément (appliquer), ou on ne garde que ceux qui passent le test (garder).
def appliquer(f, tab):
return [f(x) for x in tab]
def garder(test, tab):
return [x for x in tab if test(x)]
def composer(f, g):
def h(x): # fonction definie a l'interieur
return f(g(x))
return h # on renvoie la fonction elle-meme
composer(double, plus_un)(3) vaut double(plus_un(3)) \(= 2\times 4 = 8\), alors que composer(plus_un, double)(3) vaut plus_un(double(3)) \(= 6 + 1 = 7\) : l’ordre de composition compte.
4. La compréhension : [abs(x) for x in [-2, 3, -5]] vaut [2, 3, 5] et [x for x in [1, 2, 3, 4] if est_pair(x)] vaut [2, 4]. C’est d’ailleurs exactement le corps des fonctions appliquer et garder.
Lire et comprendre une classe#
Ici on ne programme pas (ou peu) : on lit, on repère attributs et méthodes, on prévoit l’affichage.
★ Exercice 4 — Lister attributs et méthodes #
• essentiel
On donne la classe suivante.
Coup de pouce
Les attributs sont les noms écrits self.… = … dans le constructeur ; leur type se lit sur la valeur reçue ou affectée. Les méthodes sont les fonctions définies (def) à l’intérieur de la classe.
class Livre:
def __init__(self, titre, auteur, pages):
self.titre = titre
self.auteur = auteur
self.pages = pages
self.emprunte = False
def get_titre(self):
return self.titre
def emprunter(self):
self.emprunte = True
-
Lister les attributs de la classe
Livreen précisant leur type. -
Lister les méthodes de la classe. Laquelle renvoie une information ? Laquelle modifie l’état ?
-
Quel est le rôle du mot
self?
Corrigé
-
Attributs :
titre(str),auteur(str),pages(int),emprunte(bool). -
Méthodes :
get_titrerenvoie une information (l’attributtitre) ;empruntermodifie l’état (passeemprunteàTrue). (Le constructeur__init__est aussi une méthode.) -
selfdésigne l’instance en cours : c’est par lui qu’une méthode accède aux attributs de cet objet précis.
★ Exercice 5 — Prévoir l’affichage #
En reprenant la classe Livre ci-dessus, donner sans machine ce qu’affiche ce programme, puis vérifier.
Coup de pouce
Relire le constructeur : que vaut l’attribut emprunte juste après la création de b ? Quelle méthode le modifie, et quand est-elle appelée ?
b = Livre("1984", "Orwell", 328)
print(b.get_titre())
print(b.emprunte)
b.emprunter()
print(b.emprunte)
Corrigé
L’affichage est :
1984
False
True
À la création, emprunte vaut False ; après l’appel b.emprunter(), il vaut True.
★★ Exercice 6 — Retrouver l’erreur #
Chacun de ces trois extraits contient une erreur classique de débutant en POO. La repérer et la corriger.
Coup de pouce
Relire le « réflexe » du mode d’emploi. Quel est toujours le premier paramètre d’une méthode ? Comment crée-t-on un attribut dans le constructeur ? Comment accède-t-on à l’attribut d’un objet depuis l’extérieur de la classe ?
# (a)
class Chien:
def __init__(nom):
self.nom = nom
# (b)
class Chat:
def __init__(self, nom):
nom = nom
# (c) (la classe Chien de (a) etant corrigee)
medor = Chien("Medor")
print(nom)
Corrigé
-
Il manque
selfen premier paramètre :def __init__(self, nom):. -
La ligne
nom = nomne crée aucun attribut (elle réaffecte le paramètre à lui-même) : il fautself.nom = nom. -
nomn’existe pas en dehors de la classe ; l’attribut se litmedor.nom(notation pointée).
Créer et manipuler des instances#
★ Exercice 7 — Créer des instances #
• essentiel
On donne la classe des points du plan.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
▶ 1. Écrire le code qui crée deux instances : origine au point \((0, 0)\) et p au point \((3, 5)\). 2. Afficher l’abscisse de p (attendu : 3).
Coup de pouce
Relire dans le cours comment on crée une instance : le constructeur est appelé automatiquement. Quelles valeurs faut-il fournir, et quel paramètre de __init__ ne se donne jamais ? Un attribut d’un objet se lit avec la notation pointée.
Corrigé
Démarche : on appelle la classe comme une fonction, avec les valeurs de x et y (self ne se donne pas : c’est l’objet créé) ; un attribut se lit avec la notation pointée.
origine = Point(0, 0)
p = Point(3, 5)
print(p.x) # affiche 3
★ Exercice 8 — Compléter un constructeur #
• essentiel
▶ Recopier et compléter le constructeur de la classe Eleve pour qu’elle possède trois attributs : nom, classe et une liste notes vide au départ.
Coup de pouce
Chaque attribut self.… reçoit le paramètre du même nom. Pour notes, il n’y a pas de paramètre : quelle valeur représente une liste vide ?
class Eleve:
def __init__(self, nom, classe):
self.nom = ...
self.classe = ...
self.notes = ... # liste vide au depart
e = Eleve("Zoe", "TG1")
print(e.nom, e.classe, e.notes) # attendu : Zoe TG1 []
Corrigé
Démarche : dans le constructeur, chaque attribut self.… reçoit le paramètre correspondant ; notes n’a pas de paramètre : on lui affecte une liste vide, propre à chaque élève.
class Eleve:
def __init__(self, nom, classe):
self.nom = nom
self.classe = classe
self.notes = []
★★ Exercice 9 — Ajouter des méthodes #
• essentiel
▶ En repartant de la classe Eleve précédente, ajouter deux méthodes :
-
ajouter_note(self, note)qui ajoute une note à la listenotes; -
moyenne(self)qui renvoie la moyenne des notes (on suppose la liste non vide).
??? pouce "Coup de pouce"
La liste à modifier est l’attribut `self.notes` (méthode `append`). Pour la moyenne : somme des notes (boucle d’accumulation) divisée par leur nombre, `len(self.notes)`.
Exemple d’utilisation :
>>> e = Eleve("Zoe", "TG1")
>>> e.ajouter_note(12)
>>> e.ajouter_note(16)
>>> e.moyenne()
14.0
Corrigé
Démarche : chaque méthode reçoit self en premier paramètre et agit sur l’attribut self.notes : ajouter_note le modifie (append), moyenne le parcourt avec un accumulateur, puis divise par le nombre de notes.
class Eleve:
def __init__(self, nom, classe):
self.nom = nom
self.classe = classe
self.notes = []
def ajouter_note(self, note):
self.notes.append(note)
def moyenne(self):
total = 0
for note in self.notes:
total += note
return total / len(self.notes)
▷ Erreur fréquente. Écrire notes.append(note) sans self. : dans la méthode, notes n’est pas une variable, Python lève NameError. L’attribut de l’objet s’écrit toujours self.notes.
Écrire une classe complète#
★★ Exercice 10 — La classe Rectangle #
• essentiel
▶ Écrire entièrement une classe Rectangle avec :
-
un constructeur prenant la
largeuret lahauteur; -
une méthode
aire(self)et une méthodeperimetre(self); -
une méthode
est_carre(self)qui renvoie un booléen.
??? pouce "Coup de pouce"
Chaque méthode ne fait qu’un calcul sur `self.largeur` et `self.hauteur`. `est_carre` peut renvoyer directement le résultat d’une comparaison, sans `if`.
Exemple :
>>> r = Rectangle(4, 3)
>>> r.aire()
12
>>> r.perimetre()
14
>>> r.est_carre()
False
Corrigé
Démarche : le constructeur range les deux dimensions dans des attributs ; chaque méthode ne fait qu’un calcul sur self.largeur et self.hauteur ; est_carre renvoie directement le résultat de la comparaison.
class Rectangle:
def __init__(self, largeur, hauteur):
self.largeur = largeur
self.hauteur = hauteur
def aire(self):
return self.largeur * self.hauteur
def perimetre(self):
return 2 * (self.largeur + self.hauteur)
def est_carre(self):
return self.largeur == self.hauteur
▷ Erreur fréquente. Appeler une méthode sans parenthèses (r.aire) : on obtient la méthode elle-même, pas son résultat. On écrit r.aire().
★★ Exercice 11 — La classe Chronometre #
▶ Écrire une classe Chronometre qui modélise un compteur de secondes.
-
Le constructeur initialise un attribut
secondesà0. -
tic(self)ajoute une seconde. -
reset(self)remet le compteur à0. -
affichage(self)renvoie une chaîne au format"mm:ss"(minutes et secondes sur deux chiffres).
Exemple : après \(75\) appels à tic, affichage() renvoie "01:15".
Coup de pouce
Le nombre de minutes et le nombre de secondes restantes s’obtiennent par division entière (//) et reste (%) de self.secondes par 60. f"{m:02d}" affiche un entier sur deux chiffres.
Corrigé
Le format "mm:ss" s’obtient avec la division entière (// pour les minutes) et le reste (% pour les secondes).
class Chronometre:
def __init__(self):
self.secondes = 0
def tic(self):
self.secondes = self.secondes + 1
def reset(self):
self.secondes = 0
def affichage(self):
m = self.secondes // 60
s = self.secondes % 60
return f"{m:02d}:{s:02d}"
▷ Erreur fréquente. Écrire f"{m}:{s}" : après \(75\) secondes, on obtient "1:15" et non "01:15". Le format 02d complète par un zéro à gauche.
Encapsulation#
★★ Exercice 12 — Corriger une mauvaise pratique #
• essentiel
Un élève manipule un compte directement par son attribut :
class Compte:
def __init__(self, solde):
self.solde = solde
c = Compte(100)
c.solde = c.solde - 500 # le solde devient negatif !
print(c.solde)
▶ 1. Expliquer pourquoi manipuler c.solde directement est une mauvaise pratique. 2. Réécrire la classe Compte en encapsulant le solde (attribut _solde) et en fournissant une méthode retirer(self, montant) qui refuse un retrait rendant le solde négatif (elle renvoie True si le retrait a eu lieu, False sinon) et une méthode solde(self) qui renvoie le solde.
Coup de pouce
Que peut encore garantir la classe si n’importe qui écrit dans ses attributs ? Pour retirer : comparer le montant à self._solde avant de modifier quoi que ce soit.
Corrigé
1. En écrivant directement c.solde = c.solde - 500, on court-circuite la classe : aucun contrôle n’est fait, et le solde peut devenir absurde (négatif). L’objet n’a plus la garantie de rester dans un état correct.
2. Démarche : le solde devient un attribut « interne » (_solde) ; on n’y accède plus que par les méthodes de l’interface. retirer teste d’abord le montant, et ne modifie l’état que si le retrait est accepté.
class Compte:
def __init__(self, solde):
self._solde = solde
def solde(self):
return self._solde
def retirer(self, montant):
if 0 < montant <= self._solde:
self._solde = self._solde - montant
return True
return False
Désormais c.retirer(500) sur un solde de \(100\) renvoie False et laisse le solde inchangé.
▷ Erreur fréquente. Ne tester que montant <= self._solde : c.retirer(-50) serait accepté et augmenterait le solde. La condition 0 < montant ferme cette porte.
Méthodes spéciales et objets qui interagissent#
★★★ Exercice 13 — Défi — la classe Fraction #
≈ 20 min
▶ Écrire une classe Fraction représentant un nombre rationnel par un numerateur et un denominateur.
-
La fraction sera automatiquement simplifiée à la création (diviser numérateur et dénominateur par leur PGCD).
-
Une méthode
__repr__(self)pour que la fraction s’affiche sous la forme3/4rien qu’en tapant son nom dans la console. -
Une méthode
fois(self, autre)renvoie une nouvelleFractionproduit des deux (sans modifier niselfniautre: dans l’esprit fonctionnel).
??? pouce "Coup de pouce"
Écrire d’abord, à part, une fonction `pgcd(a, b)` (algorithme d’Euclide), puis simplifier dès le constructeur. `fois` construit et renvoie un **nouvel** objet `Fraction(...)`.
??? pouce "Coup de pouce 2 (début de solution)"
`class Fraction:`
`def __init__(self, num, den):`
`d = pgcd(num, den)`
`self.numerateur = num // d`
Exemple :
>>> f = Fraction(6, 8)
>>> f
3/4
>>> print(f.fois(Fraction(2, 3)))
1/2
Corrigé
On simplifie dans le constructeur en divisant par le PGCD. On définit __repr__ (et non __str__) : ainsi la fraction s’affiche proprement en tapant simplement son nom dans la console — et print en bénéficie aussi par ricochet. La méthode fois construit et renvoie une nouvelle Fraction (elle ne modifie rien : c’est l’esprit fonctionnel).
def pgcd(a, b):
while b != 0:
a, b = b, a % b
return a
class Fraction:
def __init__(self, num, den):
d = pgcd(num, den)
self.numerateur = num // d
self.denominateur = den // d
def __repr__(self):
return f"{self.numerateur}/{self.denominateur}"
def fois(self, autre):
return Fraction(self.numerateur * autre.numerateur,
self.denominateur * autre.denominateur)
★★★ Exercice 14 — Défi — un réseau d’amis #
≈ 30 min
On modélise un petit réseau social : chaque personne est un objet de la classe Personne, qui possède un attribut nom et un attribut amis, liste d’objets Personne (vide à la création).
-
▶ Écrire le constructeur, puis la méthode
ajouter_ami(self, autre): l’amitié est réciproque (chacun est ajouté aux amis de l’autre), sans jamais créer de doublon, et une personne ne peut pas être son propre ami. -
▶ Écrire la méthode
noms_amis(self), qui renvoie la liste des noms des amis, et la méthodeamis_communs(self, autre), qui renvoie la liste des noms des amis communs àselfetautre. -
▶ Écrire la méthode
amis_d_amis(self), qui renvoie la liste, sans doublon, des noms des amis de ses amis qui ne sont niselfni déjà ses amis (les « suggestions d’amis »). -
Pourquoi la liste
amiscontient-elle des objetsPersonneplutôt que de simples noms ?
??? pouce "Coup de pouce"
Dans `ajouter_ami`, tout faire en une fois : un test pour écarter les cas interdits (`autre is self`, `autre in self.amis` ; `is` teste s’il s’agit du **même** objet, et non de deux objets égaux), puis deux `append`, un dans chaque liste. Pour `amis_d_amis` : deux boucles imbriquées (mes amis, puis les amis de chacun d’eux) et trois conditions avant d’ajouter un nom.
??? pouce "Coup de pouce 2 (début de solution)"
`def ajouter_ami(self, autre):`
`if autre is self or autre in self.amis:`
`return`
`self.amis.append(autre)`
Exemple :
>>> a, b = Personne("Ada"), Personne("Bob")
>>> c, d = Personne("Chloé"), Personne("Dan")
>>> a.ajouter_ami(b); a.ajouter_ami(c); b.ajouter_ami(c); c.ajouter_ami(d)
>>> a.ajouter_ami(b) # deja amis : rien ne change
>>> a.noms_amis(), c.noms_amis()
(['Bob', 'Chloé'], ['Ada', 'Bob', 'Dan'])
>>> a.amis_communs(b), a.amis_d_amis()
(['Chloé'], ['Dan'])
Corrigé
L’attribut amis contient des objets : un objet en connaît d’autres, et ils interagissent (une méthode de a modifie aussi l’état de autre).
class Personne:
def __init__(self, nom):
self.nom = nom
self.amis = [] # liste d'objets Personne
def ajouter_ami(self, autre):
if autre is self or autre in self.amis:
return # cas interdits : rien a faire
self.amis.append(autre)
autre.amis.append(self) # amitie reciproque
def noms_amis(self):
return [p.nom for p in self.amis]
def amis_communs(self, autre):
return [p.nom for p in self.amis if p in autre.amis]
def amis_d_amis(self):
resultat = []
for ami in self.amis:
for p in ami.amis:
if p is not self and p not in self.amis:
if p.nom not in resultat: # pas de doublon
resultat.append(p.nom)
return resultat
Sur l’exemple : a.amis_d_amis() renvoie [’Dan’] (ami de Chloé) ; Bob, déjà ami d’Ada, n’est pas proposé.
Pourquoi des objets ? En stockant les objets eux-mêmes, on accède directement à toutes leurs informations, y compris à leurs amis (indispensable pour amis_d_amis) ; deux personnes peuvent aussi porter le même nom sans être confondues. Avec de simples noms, il faudrait une structure supplémentaire pour retrouver l’objet correspondant.
Choisir un paradigme#
★ Exercice 15 — Le bon style au bon endroit #
Pour chacune des situations suivantes, indiquer le paradigme a priori le plus naturel (impératif, objet, ou fonctionnel), et justifier en une phrase.
-
modéliser les personnages, monstres et objets d’un jeu vidéo ;
-
appliquer une même transformation à tous les éléments d’une liste, sans risque d’effet de bord ;
-
dérouler un algorithme de tri étape par étape.
Coup de pouce
Relire ce qui caractérise chaque paradigme dans le cours : des objets qui ont un état et des comportements, des fonctions pures sans effet de bord, ou une suite d’instructions qui modifient des variables. Lequel décrit le mieux chaque situation ?
Corrigé
-
objet : chaque entité (personnage, monstre, objet) a un état et des comportements propres ;
-
fonctionnel : une transformation sans effet de bord évite de corrompre la liste d’origine ;
-
impératif : un tri se décrit naturellement comme une suite d’étapes qui modifient un tableau.
Vers le bac#
★★ Exercice 16 — Le domaine skiable (d’après Métropole 2024, jour 2) #
• essentiel ≈ 20 min
On ne traite ici que la partie « programmation objet » du sujet. Une station de ski modélise ses pistes par une classe Piste et son domaine par une classe Domaine. Le code est donné en annexe ci-dessous ; il se termine par la création du domaine lievre_blanc et de ses pistes.
-
Lister les attributs de la classe
Pisteen précisant leur type. -
Une piste est de couleur
’noire’si son dénivelé est \(\geqslant 100\) m ;’rouge’s’il est \(< 100\) et \(\geqslant 70\) ;’bleue’s’il est \(< 70\) et \(\geqslant 40\) ;’verte’s’il est \(< 40\). Écrire la méthodeset_couleur(self)de la classePistequi affecte à l’attributcouleurla bonne chaîne.Coup de pouce
Une cascade
if/elif/elsesurself.denivele, en partant du seuil le plus élevé : chaque test n’a alors besoin que d’une seule comparaison. -
On exécute
lievre_blanc.get_pistes(). Parmi les propositions, indiquer le type de l’élément renvoyé : (A) une chaîne ; (B) un objetPiste; (C) une liste de chaînes ; (D) une liste d’objetsPiste. -
Écrire un programme qui ferme toutes les pistes vertes, c’est-à-dire affecte
Falseà leur attributouverte. -
Écrire une fonction
pistes_de_couleur(lst, couleur)qui prend une listelstde pistes et une chaînecouleur, et renvoie la liste des noms des pistes de cette couleur.
Annexe — code des classes
class Piste:
def __init__(self, nom, denivele, longueur):
self.nom = nom
self.denivele = denivele # en metres
self.longueur = longueur # en kilometres
self.couleur = ''
self.ouverte = True
def get_nom(self):
return self.nom
def set_couleur(self):
... # A COMPLETER (question 2)
def get_couleur(self):
return self.couleur
class Domaine:
def __init__(self, a):
self.nom = a
self.pistes = []
def ajouter_piste(self, nom, denivele, longueur):
self.pistes.append(Piste(nom, denivele, longueur))
def get_pistes(self):
return self.pistes
lievre_blanc = Domaine('Le Lievre blanc')
lievre_blanc.ajouter_piste('Petit Bonheur', 120, 1.5)
lievre_blanc.ajouter_piste('Foret', 105, 2.3)
lievre_blanc.ajouter_piste('Duvallon', 140, 1.2)
lievre_blanc.ajouter_piste('Les Gentianes', 85, 2.0)
lievre_blanc.ajouter_piste('Le Chamois', 55, 1.8)
lievre_blanc.ajouter_piste('Les Marmottes', 25, 0.9)
lievre_blanc.ajouter_piste('Le Jardin', 15, 0.4)
for piste in lievre_blanc.get_pistes():
piste.set_couleur() # une fois la question 2 traitee
Corrigé
Barème indicatif (sur 3) : Q1 : 0,5 ; Q2 : 0,75 ; Q3 : 0,25 ; Q4 : 0,75 ; Q5 : 0,75
-
Attributs de
Piste:nom(str),denivele(int),longueur(float),couleur(str),ouverte(bool). -
Démarche : une cascade
if/elif/else, en partant du seuil le plus élevé : le premier test vrai l’emporte.def set_couleur(self): if self.denivele >= 100: self.couleur = 'noire' elif self.denivele >= 70: self.couleur = 'rouge' elif self.denivele >= 40: self.couleur = 'bleue' else: self.couleur = 'verte'▷ Erreur fréquente. Tester les seuils dans l’ordre croissant (
if self.denivele >= 40en premier) : une piste noire de \(120\) m serait classée bleue, car le premier test vrai l’emporte. -
Proposition (D) : une liste d’objets de type
Piste. -
Démarche : on parcourt la liste d’objets
Pisterenvoyée parget_pistes()et on modifie l’attributouvertede chaque piste verte.for piste in lievre_blanc.get_pistes(): if piste.get_couleur() == 'verte': piste.ouverte = False(
set_couleura été appelée sur chaque piste à la fin de l’annexe : ici,Les MarmottesetLe Jardinsont fermées.)▷ Erreur fréquente. Écrire
piste.ouverte == False: c’est une comparaison, qui ne modifie rien. L’affectation s’écrit avec un seul=. -
On parcourt les pistes et on garde le nom de celles qui ont la bonne couleur :
def pistes_de_couleur(lst, couleur): return [piste.get_nom() for piste in lst if piste.get_couleur() == couleur]Autre méthode (boucle et
append) :def pistes_de_couleur(lst, couleur): noms = [] for piste in lst: if piste.get_couleur() == couleur: noms.append(piste.get_nom()) return nomsPar exemple
pistes_de_couleur(lievre_blanc.get_pistes(), ’noire’)renvoie[’Petit Bonheur’, ’Foret’, ’Duvallon’].
★★ Exercice 17 — Valeurs nutritionnelles (d’après La Réunion 2023, jour 1) #
≈ 20 min
Pour chaque aliment, on stocke ses caractéristiques nutritionnelles (pour 100 g) dans un objet de la classe Aliment : énergie (kcal), protéines, glucides et lipides (en grammes).
class Aliment:
def __init__(self, e, p, g, l):
self.energie = e
self.proteines = p
self.glucides = g
self.lipides = l
-
Le lait entier a pour valeurs : énergie
65.1, protéines3.32, glucides4.85, lipides3.63. Écrire l’instruction qui crée l’instancelait. -
Donner l’instruction qui permet d’obtenir la valeur
65.1(l’énergie delait). -
Une erreur s’est glissée : la masse de protéines du lait est en réalité
3.4. Donner l’instruction qui modifie cet attribut delait. -
On veut ajouter une méthode
energie_reellequi renvoie l’énergie d’une masse donnée (en grammes). Par exemple,lait.energie_reelle(245)renvoie environ159.495(soit \(245 \times 65{,}1 \div 100\)). Recopier et compléter :def energie_reelle(..., masse): return ... -
On regroupe les aliments dans un dictionnaire
alimentsdont les clés sont les noms :aliments = {’lait’: lait, ’pain’: pain, …}. Donner l’instruction qui obtient la valeur énergétique réelle de220g de pain. -
▶ Écrire une fonction
plus_calorique(aliments)qui renvoie le nom de l’aliment le plus énergétique (pour 100 g) du dictionnairealiments.Coup de pouce
C’est une recherche de maximum (Première) : parcourir les clés du dictionnaire en mémorisant le nom du « champion » et comparer les attributs
energiedes objets associés.
Corrigé
Barème indicatif (sur 3) : Q1 : 0,25 ; Q2 : 0,25 ; Q3 : 0,25 ; Q4 : 0,75 ; Q5 : 0,5 ; Q6 : 1
-
lait = Aliment(65.1, 3.32, 4.85, 3.63). -
lait.energie(renvoie65.1). -
lait.proteines = 3.4.
4.
```python
def energie_reelle(self, masse):
return self.energie * masse / 100
```
*(`lait.energie_reelle(245)` renvoie `159.49499999999998`, soit $159{,}495$ à l’erreur d’arrondi des flottants près.)*
-
aliments[’pain’].energie_reelle(220). -
Démarche : une recherche de maximum : on parcourt les clés du dictionnaire en mémorisant le nom du « champion », et l’on compare les attributs
energiedes objets associés.def plus_calorique(aliments): nom_max = None for nom in aliments: if nom_max is None or aliments[nom].energie > aliments[nom_max].energie: nom_max = nom return nom_max
★★ Exercice 18 — La classe Date (d’après Asie 2025, jour 2) #
≈ 30 min
On manipule des dates grâce à la classe Date ci-dessous.
class Date:
def __init__(self, jour, mois, annee):
self.jour = ...
self.mois = ...
self.annee = ...
self.nb_jours_par_mois = [31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31]
def get_annee(self):
return ...
def est_bissextile(self):
... # a ecrire (question 4)
-
Recopier et compléter les trois premières lignes du constructeur (affectation des attributs
jour,moisetannee). -
Écrire l’instruction qui crée l’instance
dreprésentant le 19 juin 2024. -
Compléter la méthode
get_annee(elle renvoie l’attributannee). -
Une année est bissextile si elle est divisible par 4 mais pas par 100, ou si elle est divisible par 400. Écrire la méthode
est_bissextile(self)qui renvoieTrueouFalse. (Rappel :a % n == 0teste la divisibilité deaparn.) -
▶ Écrire la méthode
nb_jours_passes(self)qui renvoie le nombre de jours écoulés depuis le 1er janvier de l’année (jour de la date compris). On ajoutera les jours des mois précédents (vianb_jours_par_mois), sans oublier le 29 février si l’année est bissextile.Coup de pouce
Additionner
nb_jours_par_mois[i]pour les mois précédantself.mois(le mois de janvier a l’indice0), ajouterself.jour, puis1si l’année est bissextile et que la date est après février. -
▶ En déduire la méthode
nb_jours_restants(self)qui renvoie le nombre de jours restant jusqu’à la fin de l’année. (Une année compte 365 jours, ou 366 si elle est bissextile.)
Corrigé
Barème indicatif (sur 4) : Q1 : 0,5 ; Q2 : 0,25 ; Q3 : 0,25 ; Q4 : 0,75 ; Q5 : 1,25 ; Q6 : 1
-
self.jour = jour;self.mois = mois;self.annee = annee. -
d = Date(19, 6, 2024). -
return self.annee. -
Démarche : on traduit la règle de l’énoncé avec
andetor, en testant la divisibilité par le reste%.def est_bissextile(self): a = self.annee return (a % 4 == 0 and a % 100 != 0) or (a % 400 == 0)Ainsi 2024 et 2000 sont bissextiles, mais 1900 ne l’est pas.
-
Démarche : un accumulateur initialisé au jour de la date, auquel on ajoute les jours des mois précédents (indices
0àself.mois - 2), puis \(1\) si l’année est bissextile et que la date est après février.def nb_jours_passes(self): total = self.jour for m in range(self.mois - 1): # mois complets precedents total = total + self.nb_jours_par_mois[m] if self.mois > 2 and self.est_bissextile(): total = total + 1 # 29 fevrier return total -
Démarche : on réutilise
est_bissextilepour connaître le nombre de jours de l’année, puis on retranchenb_jours_passes().def nb_jours_restants(self): if self.est_bissextile(): return 366 - self.nb_jours_passes() return 365 - self.nb_jours_passes()
★★ Exercice 19 — Colorier une carte (d’après Métropole 2023, jour 1) #
≈ 15 min
On modélise une région d’un pays par la classe Region ci-dessous.
class Region:
def __init__(self, nom_region):
self.nom = nom_region
self.tab_voisines = []
self.tab_couleurs_disponibles = ['rouge', 'vert',
'bleu', 'jaune', 'orange', 'marron']
self.couleur_attribuee = None
-
Pour chacun des noms
nom,tab_voisinesetcouleur_attribuee, préciser s’il s’agit d’un objet, d’un attribut, d’une méthode ou d’une classe. Donner aussi le type du paramètrenom_region. -
Écrire l’instruction qui crée une instance
gecorrespondant à la région « Grand Est ». -
Écrire les trois méthodes suivantes de la classe
Region:-
renvoie_premiere_couleur_disponible(self): renvoie la première couleur detab_couleurs_disponibles(supposé non vide) ; -
renvoie_nb_voisines(self): renvoie le nombre de régions voisines ; -
est_coloriee(self): renvoieTruesi une couleur a été attribuée à la région,Falsesinon.
??? pouce "Coup de pouce"
Chaque méthode tient en une ligne. Que vaut `couleur_attribuee` tant qu’aucune couleur n’a été choisie ? -
Corrigé
Barème indicatif (sur 2) : Q1 : 0,5 ; Q2 : 0,25 ; Q3 : 1,25
-
nom,tab_voisinesetcouleur_attribueesont des attributs ; le paramètrenom_regionest de typestr(chaîne de caractères). -
ge = Region("Grand Est"). -
Démarche : chaque méthode lit un attribut de
self: le premier élément de la liste, sa longueur, ou le test « une couleur a-t-elle été attribuée ? ».def renvoie_premiere_couleur_disponible(self): return self.tab_couleurs_disponibles[0] def renvoie_nb_voisines(self): return len(self.tab_voisines) def est_coloriee(self): return self.couleur_attribuee is not None
★★ Exercice 20 — Des colis à expédier (d’après Amérique du Nord 2025, jour 1) #
≈ 60 min
Sujet repris en entier : programmation objet, puis tri, récursivité (revoir le chapitre Récursivité) et algorithme glouton.
Une entreprise gère les colis qu’elle expédie à l’aide d’une application. Chaque colis a un identifiant unique, un poids, une adresse de livraison et un état : ’préparé’, ’transit’ ou ’livré’. On dispose pour cela de la classe Colis suivante : à la création d’un colis, l’état vaut ’préparé’ et les autres valeurs sont passées en paramètres.
class Colis:
def __init__(self, id, poids, adresse):
self.id = id # identifiant unique (str)
self.poids = poids # poids en kilogrammes (float)
self.adresse = adresse # adresse de destination (str)
self.etat = 'préparé' # 'préparé', 'transit' ou 'livré'
On crée par exemple deux colis ; on accède au poids d’un colis c par c.poids, à son état par c.etat.
colisA = Colis('AC12', 5.0, '20 rue de la paix 57000 Metz')
colisB = Colis('AF34', 10.25, '32 rue du centre 57000 Metz')
- Écrire la méthode
passer_transitde la classeColisqui permet de mettre l’état du colis à la valeur’transit’.
On dispose de la fonction ajouter_colis ; après les trois instructions de droite, la liste liste_colis contient les deux colis créés.
def ajouter_colis(liste, colis):
# ajoute le colis a la fin de la liste
liste.append(colis)
liste_colis = []
ajouter_colis(liste_colis, colisA)
ajouter_colis(liste_colis, colisB)
-
Dans cette question uniquement, on considère que le transporteur refuse les colis de plus de 25 kg. Recopier et modifier la fonction
ajouter_colispour qu’elle ajoute le colis à la liste si son poids est inférieur ou égal à 25 kg, et qu’elle affiche le message « Dépassement du poids maximal autorisé » sinon. -
Écrire une fonction
nb_colisqui prend en paramètre une liste d’objets de la classeColiset renvoie le nombre de colis de cette liste. -
Recopier et compléter les lignes 2 et 4 de la fonction
poids_total, qui renvoie le poids total des colis de la liste.def poids_total(liste): total = ... for c in liste: total = ... return total -
Écrire une fonction
liste_colis_etatqui prend en paramètres une liste d’objets de la classeColiset une chaînestatut(parmi’préparé’,’transit’ou’livré’), et renvoie une nouvelle liste contenant les colis de cette liste dont l’état eststatut.
L’entreprise tente d’optimiser, à l’aide d’un algorithme glouton, le chargement des colis dans un camion de capacité donnée (en kilogrammes), sans tenir compte du volume : on charge les colis par ordre décroissant de poids, sans dépasser la capacité du camion. Il faut donc trier la liste des colis par poids décroissants, ce que fait la fonction tri_decroissant.
def tri_decroissant(liste):
n = len(liste)
for i in range(n - 1):
min_pos = i
for j in range(i + 1, n):
if liste[j].poids > liste[min_pos].poids:
min_pos = j
# Echanger les elements
temp = liste[i]
liste[i] = liste[min_pos]
liste[min_pos] = temp
return liste
-
Donner le nom du tri utilisé dans
tri_decroissant, ainsi que son coût dans le pire des cas. -
Citer un autre algorithme de tri qui aurait pu être utilisé, ainsi que son coût dans le pire des cas.
La fonction récursive chargement_glouton a pour paramètres liste (une liste de colis triés par poids décroissants), rang (un indice compris entre 0 inclus et len(liste) inclus) et capacite (une charge en kilogrammes). Elle renvoie la liste des colis à charger selon l’algorithme glouton, en supposant que la charge restante dans le camion est capacite et en ne considérant que les colis de liste d’indice supérieur ou égal à rang.
def chargement_glouton(liste, rang, capacite):
if rang == len(liste):
return ...
elif liste[rang].poids <= ...:
return ... + chargement_glouton(liste, ..., ...)
else:
return chargement_glouton(liste, ..., ...)
-
Recopier et compléter la fonction
chargement_glouton.Coup de pouce
Que renvoyer quand il n’y a plus de colis à examiner (une liste !) ? Si le colis de rang
rangest chargé, que deviennent le rang et la capacité restante pour l’appel suivant ? Et s’il ne l’est pas ? -
Expliquer brièvement pourquoi un appel à
chargement_gloutonpeut provoquer l’erreur suivante :RecursionError: maximum recursion depth exceeded while calling a Python object. -
▶ Écrire une fonction itérative (sans récursivité)
chargement_glouton2(liste, capacite), oùlisteest une liste de colis triés par poids décroissants etcapacitela capacité du camion en kilogrammes, qui renvoie la liste des colis à charger selon le même algorithme glouton. On pourra créer une listecolis_a_charger, puis parcourir tous les colis triés en ajoutant à cette liste chaque colis qui rentre encore dans le camion (le poids total ne doit pas excéder la capacité du camion).Coup de pouce 2 (début de solution)
def chargement_glouton2(liste, capacite):
colis_a_charger = []
charge = 0
for c in liste:
Corrigé
Barème indicatif (sur 8) : Q1 : 0,5 ; Q2 : 0,75 ; Q3 : 0,5 ; Q4 : 0,5 ; Q5 : 1 ; Q6 : 0,75 ; Q7 : 0,5 ; Q8 : 1,25 ; Q9 : 0,75 ; Q10 : 1,5
-
Méthode à placer dans la classe
Colis:def passer_transit(self): self.etat = 'transit' -
Démarche : un test sur l’attribut
poidsdu colis décide entre l’ajout et le message.def ajouter_colis(liste, colis): if colis.poids <= 25: liste.append(colis) else: print("Dépassement du poids maximal autorisé")
3.
```python
def nb_colis(liste):
return len(liste)
```
-
Ligne 2 :
total = 0; ligne 4 :total = total + c.poids. Avecliste_colis, on obtient15.25. -
On ne garde que les colis dont l’état est
statut(une nouvelle liste est créée) :def liste_colis_etat(liste, statut): return [c for c in liste if c.etat == statut] -
C’est un tri par sélection (on sélectionne le plus lourd des colis restants et on le place en position
i), de coût quadratique (\(n^2\)) dans le pire des cas. -
Par exemple le tri par insertion (vu en Première), lui aussi de coût quadratique (\(n^2\)) dans le pire des cas. Le tri fusion, que l’on étudiera au chapitre Diviser pour régner, fera mieux : \(n \log_2 n\).
-
Démarche : condition d’arrêt : plus de colis à examiner (on renvoie une liste vide) ; si le colis de rang
rangtient, on le charge et la capacité diminue de son poids ; sinon on passe au suivant sans le charger.def chargement_glouton(liste, rang, capacite): if rang == len(liste): return [] elif liste[rang].poids <= capacite: return [liste[rang]] + chargement_glouton(liste, rang + 1, capacite - liste[rang].poids) else: return chargement_glouton(liste, rang + 1, capacite) -
Chaque appel récursif augmente
rangde 1 : pour une liste de \(n\) colis, il y a \(n + 1\) appels imbriqués. Python limite la profondeur de la pile d’appels (environ 1000 par défaut) : avec une liste de plusieurs milliers de colis, la limite est dépassée et Python lèveRecursionError. -
Démarche : on parcourt les colis dans l’ordre en tenant à jour la charge du camion ; on garde un colis seulement s’il tient encore.
def chargement_glouton2(liste, capacite): colis_a_charger = [] charge = 0 for c in liste: if charge + c.poids <= capacite: colis_a_charger.append(c) charge = charge + c.poids return colis_a_chargerElle applique la même règle que
chargement_glouton(on garde un colis s’il tient dans la charge restante, en parcourant la liste dans le même ordre) : elle renvoie donc la même liste quechargement_glouton(liste, 0, capacite).
★★ Exercice 21 — Puissance 4 : la grille de jeu (d’après Amérique du Nord 2026, jour 1, partie A) #
≈ 30 min
Le jeu puissance 4 se joue à deux joueurs dans une grille de 6 lignes et 7 colonnes. Une couleur de pion, blanc ou noir, est attribuée à chaque joueur ; ils jouent à tour de rôle. À son tour, un joueur choisit une colonne dans laquelle il introduit un pion de sa couleur : la grille étant verticale, le pion tombe et bute soit sur le bas de la grille, soit sur un pion déjà placé. Le but est d’aligner au moins quatre pions de sa couleur dans n’importe quelle direction (horizontalement, verticalement, en diagonale). Le premier à y parvenir a gagné.
Figure 1. Une partie gagnée par le joueur blanc (numéros des lignes à gauche, des colonnes en bas).
Dans cet exercice, on programme la grille de jeu et le calcul de son score. La suite du sujet (l’algorithme min-max, qui explore un arbre de coups) sera traitée au chapitre Arbres.
Les deux joueurs sont notés 1 et 2. L’unique attribut d’une instance de la classe Grille est un tableau grille de 6 lignes et 7 colonnes (liste de listes) contenant des entiers : 0 pour une case vide, 1 (resp. 2) pour un pion du joueur 1 (resp. 2). Les lignes et les colonnes sont numérotées comme sur la figure 1 (ligne 0 en haut).
-
Écrire la méthode
__init__(self)de la classeGrillequi définit l’attributgrillecomme un tableau rempli de0. -
La méthode
joue(self, colonne, joueur)tente de placer un pion du joueurjoueurdans la colonnecolonne. Si la colonne ne contient pas déjà six pions, le pion est placé au bon endroit et la méthode renvoieTrue; sinon, elle renvoie simplementFalse. La recherche d’une case libre se fait de bas en haut. Recopier et compléter les lignes 4, 6, 7 et 9.def joue(self, colonne, joueur): ligne = 5 # on part de la rangee la plus basse while ligne != -1 and self.grille[ligne][colonne] != 0: ligne = ... if ligne != -1: self.grille[ligne][colonne] = ... return ... else: return ...
Voici le tableau représentant la grille de jeu à la fin du troisième coup d’une partie :
[[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 2, 0, 0, 0]]
- Écrire le code Python qui crée cette grille de jeu en utilisant uniquement les méthodes de la classe
Grille. Elle sera stockée dans une variablejeu1.
On suppose écrite une fonction valeur_case(ligne, colonne) qui renvoie le nombre d’alignements de quatre cases contenant la case (ligne, colonne). Par exemple, valeur_case(0, 1) renvoie 4 : les quatre alignements de quatre cases contenant la case (0, 1) sont représentés sur la figure 2.
Figure 2. Les alignements contenant la case (0, 1) (marquée d’un point blanc).
Le score d’une grille s’obtient en additionnant les valeurs de toutes les cases occupées par un pion du joueur 2 et en soustrayant les valeurs de toutes les cases occupées par un pion du joueur 1 (la valeur d’une case étant donnée par valeur_case).
-
Donner le score de la grille
jeu1de la question 3, en expliquant le calcul. -
Écrire la méthode
score(self)qui renvoie le score de la grille représentée par l’objet.Coup de pouce
Deux boucles imbriquées sur les 6 lignes et les 7 colonnes de
self.grille; que faire de la valeur renvoyée parvaleur_caseselon que la case contient0,1ou2?
Corrigé
Barème indicatif (sur 4) : Q1 : 0,75 ; Q2 : 1 ; Q3 : 0,5 ; Q4 : 0,5 ; Q5 : 1,25
1.
```python
def __init__(self):
self.grille = [[0 for j in range(7)] for i in range(6)]
```
<span class="erreurfreq">▷ Erreur fréquente.</span> Écrire `[[0] * 7] * 6` : on crée six références vers la **même** ligne, et un pion posé dans une ligne apparaîtrait dans les six.
-
Ligne 4 :
ligne = ligne - 1(on remonte d’une rangée) ; ligne 6 :self.grille[ligne][colonne] = joueur; ligne 7 :return True; ligne 9 :return False(colonne pleine). -
Le joueur 1 commence : il joue en colonne 2, le joueur 2 en colonne 3, puis le joueur 1 à nouveau en colonne 3 (son pion se pose sur celui du joueur 2).
jeu1 = Grille() jeu1.joue(2, 1) jeu1.joue(3, 2) jeu1.joue(3, 1) -
Case
(5, 3)(joueur 2) : 7 alignements (4 horizontaux, 1 vertical, 1 dans chaque diagonale). Case(4, 3)(joueur 1) : 10 alignements (4 horizontaux, 2 verticaux, 2 dans chaque diagonale). Case(5, 2)(joueur 1) : 5 alignements (3 horizontaux, 1 vertical, 1 diagonal). Score : \(7 - (10 + 5) = -8\). -
Démarche : deux boucles imbriquées parcourent les cases ; on ajoute la valeur des cases du joueur 2 et l’on retranche celle des cases du joueur 1.
def score(self): s = 0 for ligne in range(6): for colonne in range(7): if self.grille[ligne][colonne] == 2: s = s + valeur_case(ligne, colonne) elif self.grille[ligne][colonne] == 1: s = s - valeur_case(ligne, colonne) return s
Critiquer une réponse d’IA#
★★ Exercice 22 — Une classe écrite par un assistant d’IA #
Un élève demande à un assistant d’IA : « Écris une classe Python Panier avec le nom du client et la liste de ses articles (vide au départ), une méthode ajouter et une méthode nb_articles. » Voici la réponse obtenue :
class Panier:
def __init__(self, client, articles=[]):
self.client = client
self.articles = articles # liste vide par defaut
def ajouter(self, article):
self.articles.append(article)
def nb_articles(self):
return len(self.articles)
« Le paramètre articles=[] donne une liste vide à chaque nouveau panier, sauf si l’on en fournit une ; ajouter l’enrichit et nb_articles renvoie sa longueur. »
-
La réponse est-elle correcte ? ▶ Prévoir l’affichage du scénario suivant, puis l’exécuter :
p1 = Panier("Alice") p2 = Panier("Bob") p1.ajouter("stylo") print(p1.nb_articles(), p2.nb_articles()) -
Localiser et corriger l’erreur.
Coup de pouce
La valeur par défaut
[]est évaluée quand Python lit la lignedef. Combien de listes sont donc créées : une par panier, ou une seule pour tous ? -
Quelle vérification simple, faite avant de faire confiance à la réponse, aurait suffi à détecter l’erreur ?
Corrigé
1. Non. On attend 1 0 (Bob n’a rien ajouté) ; l’exécution affiche 1 1, et p2.articles vaut [’stylo’] : le stylo d’Alice est aussi dans le panier de Bob.
2. L’erreur : la valeur par défaut articles=[] est créée une seule fois, à la définition de la classe, et partagée par toutes les instances qui ne fournissent pas de liste (une liste est un objet mutable : append modifie cette liste commune). Correction : créer la liste dans le constructeur, pour chaque instance :
class Panier:
def __init__(self, client):
self.client = client
self.articles = [] # une liste NEUVE par panier
(le reste de la classe est inchangé). Le scénario affiche alors 1 0.
3. Créer deux instances, modifier l’une, vérifier que l’autre n’a pas bougé : un test avec un seul objet ne peut pas révéler un état partagé. Réflexe : jamais de liste (ni de dictionnaire) comme valeur par défaut d’un paramètre.
S’entraîner à l’oral#
★★ Exercice 23 — Expliquer en deux minutes #
Au Grand Oral comme devant l’examinateur de l’épreuve pratique, il faut savoir expliquer une notion clairement, sans lire ses notes. Choisir l’un des sujets suivants (ou le tirer au sort) :
-
Expliquer à un camarade de Première la différence entre une classe et une instance.
-
Expliquer à quoi sert l’encapsulation : pourquoi passer par des méthodes plutôt que modifier directement les attributs ?
-
Expliquer le rôle de la méthode
__init__et du paramètreself.
Préparation (5 min, seul). Noter au brouillon trois idées et un exemple, rien de plus : comme au Grand Oral, ces notes servent d’aide-mémoire. Exposé (2 min, chronométré). Debout, face à un camarade, sans lire ses notes et sans rien écrire : tout passe par la parole. Retour (2 min). Le camarade pose une question de relance (on peut alors s’aider d’un schéma), remplit la grille ci-dessous et donne un conseil ; puis on échange les rôles. Cette grille reprend les critères d’évaluation du Grand Oral.
Coup de pouce
Structurer chaque explication en trois temps : une définition en une phrase, un exemple petit et concret déroulé à voix haute, puis le piège (l’erreur que l’auditeur risque de faire). Finir par une phrase qui répond à la question posée. Choisir un objet de la vie courante (un chien, un compte bancaire, une voiture) et s’y tenir du début à la fin : ses attributs, ses méthodes, deux objets différents.
| Grille entre camarades | Oui | En partie | Pas encore |
|---|---|---|---|
| Clair — audible, posé ; chaque mot technique est expliqué | ▫ | ▫ | ▫ |
| Juste — c’est exact, et l’exemple montre vraiment l’idée | ▫ | ▫ | ▫ |
| Construit — un fil conducteur, tenu en deux minutes, sans lire | ▫ | ▫ | ▫ |
| Un conseil pour la prochaine fois : | |||
Corrigé
Pas de texte à apprendre par cœur : voici les éléments attendus pour chaque sujet. L’ordre, les mots et l’exemple peuvent être différents ; l’explication est réussie si ces idées y sont, justes et reliées entre elles.
Sujet 1.
-
Une classe est un modèle (un plan, un moule) qui décrit les attributs et les méthodes communs ; une instance est un objet concret fabriqué à partir de ce modèle, avec ses propres valeurs d’attributs.
-
Exemple : la classe
Chien;rex = Chien(’Rex’, 4)etmedor = Chien(’Médor’, 2)sont deux instances de la même classe. -
Modifier
rex.agene change pasmedor.age: chaque instance a son propre état. -
Piège : confondre attribut (une donnée) et méthode (une action), ou dire que la classe « contient » les objets.
Sujet 2.
-
Encapsulation : l’état d’un objet (ses attributs) n’est modifié qu’à travers les méthodes de son interface ; l’utilisateur n’a pas à connaître l’implémentation.
-
Exemple : un compte bancaire dont la méthode
retirer(montant)vérifie que le solde suffit ; écrire directementcompte._solde = -500contourne la règle. -
Intérêt : on peut changer l’implémentation interne sans casser le code qui utilise la classe, et les règles de cohérence sont vérifiées à un seul endroit.
-
En Python, le tiret bas (
_solde) est une convention : rien n’est techniquement interdit, mais on s’engage à passer par les accesseurs et mutateurs.
Sujet 3.
-
__init__est le constructeur : il est appelé automatiquement à la création de l’objet (Chien(’Rex’, 4)) et initialise ses attributs. -
selfdésigne l’instance sur laquelle la méthode agit :rex.aboyer()revient àChien.aboyer(rex). -
Exemple : dans
self.nom = nom,self.nomest l’attribut de l’objet,nomle paramètre reçu. -
Piège : oublier
selfdans la liste des paramètres, ou écrirenom = nom(une variable locale, perdue à la fin de la méthode).