Terminale · Structures de données

Programmation objet : classes, objets, attributs et méthodes

Deux personnages d’un jeu utilisent les mêmes règles, mais ne possèdent pas nécessairement la même énergie. Une classe décrit leur fonctionnement commun ; chaque objet conserve son propre état. Observer plusieurs instances rend cette séparation immédiatement concrète.

SofienAvec SofienIngénieur et enseignant en informatique
Dans ce chapitre

Un cap pour ce chapitre

Ce que vous saurez faire

  • Écrire une classe simple et créer des instances.
  • Lire et modifier les attributs d’un objet.
  • Expliquer les paramètres et effets d’une méthode.
Les bases utiles pour commencer

Une classe décrit, une instance existe

Une classe définit une organisation de données et des opérations associées. Un objet créé à partir de cette classe est une instance. Pour des personnages, les attributs peuvent être le nom et l’énergie ; les méthodes peuvent permettre de se déplacer ou de se reposer. Deux instances partagent le code des méthodes mais possèdent des états distincts.

Il faut distinguer le nom de la classe et les variables qui référencent les objets. La classe Personnage n’est pas elle-même un personnage particulier du jeu. Les variables alice et bob peuvent désigner deux objets construits avec les mêmes règles, mais initialisés avec des noms différents.

Initialiser des attributs cohérents

En Python, la méthode __init__ initialise l’instance lorsqu’elle est créée. Le premier paramètre, conventionnellement nommé self, désigne l’objet concerné. L’affectation self.energie = 10 crée un attribut de cette instance. Une simple variable locale nommée energie ne conserverait pas automatiquement son état dans l’objet après l’appel.

Un constructeur doit établir les propriétés attendues : une énergie non négative, par exemple. Les règles de modification doivent ensuite les préserver. Le programme de Terminale demande de maîtriser ces mécanismes de base ; l’héritage et le polymorphisme ne sont pas nécessaires pour les exemples de ce chapitre.

class Personnage:
    def __init__(self, nom):
        self.nom = nom
        self.energie = 10

    def avancer(self, cout):
        assert cout >= 0
        if self.energie >= cout:
            self.energie -= cout
            return True
        return False

Dans alice.avancer(3), le coût 3 est un argument explicite et Alice fournit l’objet associé à self. À l’intérieur, self.energie lit donc l’énergie d’Alice. Appeler la même méthode sur Bob désigne un autre état. Cette lecture remplace l’idée trompeuse d’une énergie unique partagée automatiquement par tous les objets de la classe.

Appeler une méthode sur le bon objet

L’appel alice.avancer(3) demande l’opération à l’objet référencé par alice. Python fournit cet objet comme self ; le programme indique seulement le coût. Si Alice avait dix unités, elle en possède ensuite sept et la méthode renvoie True. Bob reste à dix tant qu’aucune opération ne modifie son propre état.

La valeur renvoyée et l’effet sur l’objet sont distincts. Ici, le booléen indique si l’action est possible ; l’attribut mémorise l’énergie restante. Confondre les deux peut conduire à remplacer accidentellement une référence d’objet par True ou False.

Protéger les règles du modèle

Un coût supérieur à l’énergie disponible est refusé sans modifier l’état. Un coût nul réussit sans consommer d’énergie. Un coût négatif est exclu par le contrat : sinon, soustraire ce coût augmenterait l’énergie. Tester ces limites permet de vérifier le comportement voulu, pas seulement un déplacement ordinaire.

Deux variables peuvent également désigner le même objet après une affectation comme copie = alice. Ce n’est pas une nouvelle instance ; les modifications seront alors partagées. L’atelier commence avec deux instances distinctes et permet ensuite de remplacer Bob par un alias d’Alice. Le chapitre sur les références approfondit ensuite la situation où plusieurs noms désignent un seul objet.

Un test d’objet doit observer deux résultats : la valeur renvoyée par la méthode et l’état après son appel. Pour un refus, vérifier seulement False ne suffit pas si la méthode a tout de même soustrait le coût. Pour une réussite, vérifier seulement l’énergie ne prouve pas que le booléen renvoyé respecte le contrat.

Exemple suivi : plusieurs appels sur le même objet

Alice commence avec 10 unités. Trois appels de coût 4 produisent successivement True et 6, True et 2, puis False et 2. Le troisième appel ne consomme rien, car la condition d’autorisation n’est pas satisfaite. Bob, créé séparément, conserve 10. Chaque ligne reprend l’énergie obtenue à la ligne précédente.

Écrire alice = alice.avancer(4) serait une autre opération : après l’appel, la variable Alice référencerait le booléen retourné au lieu du personnage. Pour conserver le personnage et mémoriser le résultat, utilisez un autre nom, par exemple reussi = alice.avancer(4). La méthode transforme l’état de l’objet ; son retour fournit une information sur cette transformation. Les deux mécanismes doivent rester séparés dans une trace.

Concevoir une autre classe avec un invariant

Une classe de compteur limité peut posséder deux attributs : valeur, initialement zéro, et limite, choisie non négative. Une méthode d’ajout n’accepte un entier positif que si la nouvelle valeur ne dépasse pas la limite. L’invariant d’objet est 0 <= valeur <= limite. Le constructeur l’établit ; chaque méthode doit le conserver.

Pour une limite de 5, ajouter 3 réussit et donne 3 ; ajouter 4 est refusé et conserve 3 ; ajouter 2 atteint 5. Une méthode ne doit pas produire une valeur 7 puis annoncer simplement un refus sans rétablir l’état. Définissez donc le contrat avant le code : paramètres, résultat, effets et frontières. Ce travail utilise les classes, attributs et méthodes du programme, sans nécessiter l’héritage ni d’autres mécanismes plus avancés.

À vous de faire varier les choses

Deux personnages, deux réserves d’énergie

Choisissez le personnage, le coût et le nombre d’actions successives. Vérifiez ce qui change sur l’autre instance et testez un coût nul ou trop grand.

Lire le résultat de l’expérience initiale

Alice : 4 ; Bob : 10

Les deux objets sont créés séparément. Un refus conserve l’état ; un coût nul est accepté sans consommation.

Appelself.nomAvantRésultatAprès
1Alice10True7
2Alice7True4

Le code est commun, mais self oriente chaque appel vers l’état de l’instance choisie.

De la compréhension à l’autonomie

À vous de résoudre

Cherchez d’abord par vous-même. Vérifiez les résultats demandés, utilisez les indices si nécessaire, puis comparez votre méthode à la correction.

Exercice 1 · Appliquer#

Deux créations distinctes

On crée alice = Personnage("Alice") et bob = Personnage("Bob"), puis alice.avancer(4). Donnez les énergies finales et le résultat de la méthode.

Indice 1

Chaque appel de la classe crée ici une instance distincte.

Indice 2

Le coût concerne uniquement self dans la méthode appelée.

Comprendre la correction

Alice possède 6 unités, Bob en possède toujours 10 et la méthode renvoie True. Les instances partagent la définition de avancer, mais pas leur attribut energie. Le nom self prend son sens à chaque appel en désignant l’objet utilisé.

Exercice 2 · Comprendre#

Une action refusée

Alice possède 6 unités et appelle avancer(8). Quel est le résultat et quel est l’état après l’appel ?

Indice 1

La comparaison de disponibilité est fausse.

Indice 2

La soustraction se trouve uniquement dans la branche autorisée.

Comprendre la correction

La méthode renvoie False et l’énergie reste à 6. Le refus ne consomme rien dans ce contrat. Un programme qui soustrairait d’abord puis testerait pourrait créer une énergie négative et ne respecterait plus cette règle.

Exercice 3 · Corriger#

Un attribut oublié

Dans __init__, un élève écrit energie = 10 au lieu de self.energie = 10. Pourquoi la méthode avancer rencontre-t-elle un problème ?

Indice 1

Une variable locale appartient à l’exécution de la fonction.

Indice 2

La méthode cherche un attribut de l’instance.

Comprendre la correction

L’affectation crée une variable locale et n’initialise pas l’attribut attendu. L’accès self.energie dans avancer ne trouve donc pas l’énergie sur cet objet. Il faut écrire self.energie = 10 pour conserver cette information dans chaque instance.

Exercice 4 · Justifier#

Une affectation n’est pas une création

Après alias = alice, on appelle alias.avancer(2). L’énergie lue par alice change-t-elle ? Expliquez la différence avec une nouvelle construction.

Indice 1

Aucun appel de Personnage n’apparaît dans cette affectation.

Indice 2

Les deux noms peuvent désigner la même instance.

Comprendre la correction

Oui. alias et alice désignent le même objet ; l’appel modifie donc l’énergie observée par les deux noms. Pour obtenir un personnage distinct, il faut créer une autre instance et choisir son état initial. Une affectation de référence ne copie pas automatiquement les attributs.

Exercice 5 · Approfondir et transférer#

Trois appels, deux réussites

Alice et Bob sont créés séparément avec énergie 10. Alice avance trois fois pour un coût de 4. Donnez les trois retours, les énergies finales et un test qui détecterait une consommation lors du refus.

Indice 1

Le troisième coût dépasse l’énergie restante.

Indice 2

Le refus doit conserver 2 et non produire une énergie négative.

Comprendre la correction

Les retours sont True,True,False. Alice reste à 2 et Bob à 10. Après le troisième appel, on vérifie simultanément le retour False et l’énergie égale à 2. Vérifier le seul booléen laisserait passer une méthode qui soustrait avant de refuser.

Exercice 6 · Approfondir et transférer#

Deux noms pour un personnage

On crée Alice avec énergie 10, puis alias=alice et bob=Personnage("Bob"). On appelle alias.avancer(3), puis alice.avancer(2). Combien d’objets ont été créés et quelles énergies voit-on par les trois noms ?

Indice 1

Une affectation de référence ne construit pas une instance.

Indice 2

Les deux premiers noms observent les mêmes modifications.

Comprendre la correction

Deux objets ont été créés. Alice et alias désignent le même personnage, dont l’énergie devient 7 puis 5. Bob reste à 10. Les trois lectures donnent donc 5,5,10. Compter les variables plutôt que les constructions aurait confondu noms et objets.

Exercice 7 · Approfondir et transférer#

Spécifier un compteur limité

Un compteur de limite 5 commence à 0. Sa méthode ajouter reçoit un entier non négatif, refuse sans modification si la limite serait dépassée et renvoie un booléen. Déroulez les ajouts 3,4,2. Proposez les tests de frontière nécessaires.

Indice 1

Comparez la valeur candidate à la limite avant l’affectation.

Indice 2

Testez aussi un ajout nul et une égalité exacte à la limite.

Comprendre la correction

Les états sont 3,3,5 et les retours True,False,True. Un ajout nul doit réussir sans modifier la valeur. Un ajout atteignant exactement 5 doit réussir ; un ajout dépassant 5 doit être refusé avec état conservé. Un argument négatif est exclu par le contrat annoncé.

Les erreurs qui méritent un détour

Donner self explicitement dans un appel usuel objet.methode(...).
Python associe déjà l’objet à self. Les arguments écrits correspondent aux paramètres suivants.
Confondre méthode et valeur de retour.
Une méthode peut modifier un état et renvoyer une information différente. Suivez les deux séparément.

La fiche à garder

L’essentiel à retenir

  • La classe définit un fonctionnement commun aux instances.
  • self désigne l’instance qui reçoit l’appel.
  • Attributs, effets et résultats doivent être distingués.

Cette notion au bac

Retrouvez ces idées dans un sujet complet, avec des indices, une correction expliquée et des ateliers.

Le prochain pas

Retrouver le catalogue de Terminale

Ce chapitre s’appuie sur le programme officiel de Terminale (PDF, nouvel onglet). Les explications et exercices sont proposés pour l’apprentissage.