Pourquoi une classe ?
Jusqu'ici, vos données et vos fonctions vivaient séparément : un dictionnaire pour le compte d'Alice d'un côté, une fonction deposer() de l'autre. Ça marche, mais dès que les objets se multiplient, on s'y perd. Une classe permet de regrouper au même endroit les données et les comportements qui vont ensemble.
L'image classique : la classe est un moule, et chaque objet créé à partir d'elle est un gâteau sorti de ce moule. Un seul moule, autant de gâteaux que vous voulez, chacun avec ses propres valeurs.
Cette leçon montre la POO côté Python (la syntaxe). Si tu veux vraiment comprendre les concepts de la programmation orientée objet (encapsulation, héritage, polymorphisme), suivez le cours dédié à la POO, qui est agnostique du langage.
Définir une classe : class, __init__ et self
On définit une classe avec le mot-clé class. La méthode spéciale __init__ est le constructeur : Python l'appelle automatiquement à chaque création d'objet pour l'initialiser.
Voici une classe avec trois paramètres dans __init__ : self, titulaire et solde. Pourtant on l'instancie avec seulement deux arguments : a = Compte("Alice", 100). Avant de dérouler : pourquoi ça ne provoque pas d'erreur, où passe self ? Et si on crée aussi b = Compte("Bob", 0) puis qu'on fait a.solde += 50, le solde de b change-t-il ?
Voir la réponse
Python passe automatiquement l'objet en cours de création comme premier argument self : on ne le fournit jamais soi-même. D'où deux arguments à l'appel pour trois paramètres. self désigne « cet objet-ci » et sert à stocker les attributs dessus (self.solde). a et b sont deux objets indépendants : chacun a son propre self, donc son propre solde. Modifier a.solde ne touche pas b.solde.
class Compte:
def __init__(self, titulaire, solde):
self.titulaire = titulaire
self.solde = solde
# On crée deux instances DISTINCTES à partir de la même classe
c = Compte("Alice", 100)
autre = Compte("Bob", 0)
print(c.titulaire) # Alice
print(c.solde) # 100
print(autre.solde) # 0
Le paramètre self représente l'objet en cours de manipulation : « mon propre titulaire », « mon propre solde », pas ceux du voisin. C'est lui qui rend chaque compte indépendant.
Tu écris Compte("Alice", 100) avec deux arguments, alors que __init__ en déclare trois (self, titulaire, solde). C'est normal : Python passe automatiquement l'objet lui-même en premier paramètre. Tu ne fournis jamais self toi-même.
Attributs d'instance et méthodes
Les variables stockées sur l'objet (self.titulaire, self.solde) sont ses attributs. Les fonctions définies dans la classe sont ses méthodes : elles prennent toujours self en premier paramètre, ce qui leur donne accès à l'état de l'objet.
class Compte:
def __init__(self, titulaire, solde=0):
self.titulaire = titulaire
self.solde = solde
def deposer(self, montant):
self.solde += montant
def afficher_solde(self):
print(f"Solde de {self.titulaire} : {self.solde} euros")
c = Compte("Alice", 100)
c.deposer(50)
c.afficher_solde() # Solde de Alice : 150 euros
# Bob a son propre état, indépendant
bob = Compte("Bob")
bob.afficher_solde() # Solde de Bob : 0 euros
On appelle une méthode avec la notation pointée : c.deposer(50). Là encore, self est passé automatiquement : tu ne donnes que montant.
Passe toujours par les méthodes pour modifier l'état (ici deposer()) plutôt que de toucher l'attribut directement. C'est le principe d'encapsulation : la classe garde le contrôle de ses propres données.
__str__ : un affichage lisible
Que se passe-t-il si on fait print() directement sur un objet ? Par défaut, Python affiche quelque chose d'illisible :
class Compte:
def __init__(self, titulaire, solde):
self.titulaire = titulaire
self.solde = solde
c = Compte("Alice", 150)
print(c) # <__main__.Compte object at 0x7f3a...>
Pour obtenir un affichage propre, on définit la méthode spéciale __str__. Elle doit renvoyer une chaîne, que print() utilisera automatiquement :
class Compte:
def __init__(self, titulaire, solde):
self.titulaire = titulaire
self.solde = solde
def __str__(self):
return f"Compte de {self.titulaire} : {self.solde} euros"
c = Compte("Alice", 150)
print(c) # Compte de Alice : 150 euros
Même principe que __init__ : c'est une méthode « magique » (encadrée de doubles soulignés) que Python appelle pour vous au bon moment, ici dès qu'on tente d'afficher l'objet ou de le convertir en texte avec str().
L'héritage : une classe qui prolonge une autre
Parfois, deux classes partagent une base commune. Un Chien est un Animal : il a un nom, il peut se présenter, ET il peut en plus aboyer. Plutôt que de tout réécrire, on fait hériter Chien de Animal.
class Animal:
def __init__(self, nom):
self.nom = nom
def se_presenter(self):
print(f"Je suis {self.nom}")
class Chien(Animal): # Chien herite d Animal
def __init__(self, nom, race):
super().__init__(nom) # le parent s occupe du nom
self.race = race # Chien ajoute la race
def aboyer(self):
print(f"{self.nom} dit : Ouaf !")
rex = Chien("Rex", "Labrador")
rex.se_presenter() # Je suis Rex <- methode heritee
rex.aboyer() # Rex dit : Ouaf ! <- methode propre a Chien
class Chien(Animal): signifie « Chien prolonge Animal ». super().__init__(nom) appelle le constructeur du parent : on ne réécrit pas ce qu'il fait déjà, on l'invoque et on ajoute ce qui est spécifique au chien. Si on définit une méthode du même nom que dans le parent, elle la remplace (on parle de redéfinition).
Dans la leçon sur la gestion des erreurs, vous verrez class MonErreur(Exception): pass. C'est exactement ce mécanisme : une exception personnalisée qui hérite d'Exception.
Avant d'utiliser l'héritage, posez-vous la question « est-un » : un Chien est un Animal, l'héritage a du sens. Si la relation est plutôt « a un » (un Panier a des articles), préférez la composition (un attribut qui stocke l'autre objet). Le cours dédié à la POO détaille ce choix.
Récap
class Nom:définit le moule.__init__(self, ...)est le constructeur, appelé à chaque création d'objet.selfdésigne l'objet courant ; il est passé automatiquement, jamais à la main.- Les attributs (
self.x) sont les données ; les méthodes (fonctions avecself) sont les comportements. __str__(self)renvoie une chaîne lisible utilisée parprint().class Enfant(Parent):hérite ;super().__init__(...)invoque le constructeur parent.
Why a class?
So far, your data and your functions lived apart: a dictionary for Alice's account on one side, a deposit() function on the other. It works, but as soon as objects multiply, things get messy. A class lets you group together the data and the behaviors that belong together.
The classic image: the class is a mold, and each object built from it is a cake baked in that mold. One mold, as many cakes as you want, each with its own values.
This lesson shows OOP from the Python side (the syntax). If you want to really understand the concepts of object-oriented programming (encapsulation, inheritance, polymorphism), follow the dedicated OOP course, which is language-agnostic.
Defining a class: class, __init__ and self
You define a class with the class keyword. The special __init__ method is the constructor: Python calls it automatically every time an object is created, to initialize it.
Here is a class with three parameters in __init__: self, titulaire and solde. Yet we instantiate it with only two arguments: a = Compte("Alice", 100). Before you scroll down: why doesn't this raise an error, where does self go? And if we also create b = Compte("Bob", 0) then do a.solde += 50, does b's balance change?
See the answer
Python automatically passes the object being created as the first argument self: you never supply it yourself. That's why two arguments at the call site match three parameters. self means "this particular object" and is used to store attributes on it (self.solde). a and b are two independent objects: each has its own self, hence its own solde. Modifying a.solde does not affect b.solde.
class Account:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
# We create two DISTINCT instances from the same class
a = Account("Alice", 100)
other = Account("Bob", 0)
print(a.owner) # Alice
print(a.balance) # 100
print(other.balance) # 0
The self parameter refers to the object currently being handled: "my own owner", "my own balance", not the neighbor's. That's what makes each account independent.
You write Account("Alice", 100) with two arguments, while __init__ declares three (self, owner, balance). That's normal: Python automatically passes the object itself as the first parameter. You never supply self yourself.
Instance attributes and methods
The variables stored on the object (self.owner, self.balance) are its attributes. The functions defined inside the class are its methods: they always take self as the first parameter, which gives them access to the object's state.
class Account:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
def show_balance(self):
print(f"{self.owner}'s balance: {self.balance} euros")
a = Account("Alice", 100)
a.deposit(50)
a.show_balance() # Alice's balance: 150 euros
# Bob has his own independent state
bob = Account("Bob")
bob.show_balance() # Bob's balance: 0 euros
You call a method with dotted notation: a.deposit(50). Here too, self is passed automatically: you only give amount.
Always go through methods to change state (here deposit()) rather than touching the attribute directly. That's the principle of encapsulation: the class keeps control over its own data.
__str__: readable output
What happens if you print() an object directly? By default, Python shows something unreadable:
class Account:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
a = Account("Alice", 150)
print(a) # <__main__.Account object at 0x7f3a...>
To get clean output, define the special __str__ method. It must return a string, which print() will use automatically:
class Account:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def __str__(self):
return f"{self.owner}'s account: {self.balance} euros"
a = Account("Alice", 150)
print(a) # Alice's account: 150 euros
Same principle as __init__: it's a "magic" method (wrapped in double underscores) that Python calls for you at the right moment, here whenever you try to display the object or convert it to text with str().
Inheritance: a class that extends another
Sometimes two classes share a common base. A Dog is an Animal: it has a name, it can introduce itself, AND it can also bark. Rather than rewriting everything, we make Dog inherit from Animal.
class Animal:
def __init__(self, name):
self.name = name
def introduce(self):
print(f"I am {self.name}")
class Dog(Animal): # Dog inherits from Animal
def __init__(self, name, breed):
super().__init__(name) # let the parent handle name
self.breed = breed # Dog adds breed
def bark(self):
print(f"{self.name} says: Woof!")
rex = Dog("Rex", "Labrador")
rex.introduce() # I am Rex <- inherited method
rex.bark() # Rex says: Woof! <- Dog's own method
class Dog(Animal): means "Dog extends Animal". super().__init__(name) calls the parent constructor: we don't rewrite what it already does, we call it and add what is specific to the dog. If we define a method with the same name as in the parent, it overrides it.
In the error handling lesson you'll see class MyError(Exception): pass. That's exactly this mechanism: a custom exception that inherits from Exception.
Before using inheritance, ask yourself "is-a": a Dog is an Animal, inheritance makes sense. If the relationship is more "has-a" (a Cart has items), prefer composition (an attribute that holds the other object). The dedicated OOP course covers this choice in depth.
Recap
class Name:defines the mold.__init__(self, ...)is the constructor, called every time an object is created.selfrefers to the current object; it's passed automatically, never by hand.- Attributes (
self.x) are the data; methods (functions withself) are the behaviors. __str__(self)returns a readable string used byprint().class Child(Parent):inherits;super().__init__(...)calls the parent constructor.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
✨ Prompt IA
Copiez ce prompt dans Claude ou ChatGPT :
Écris une classe Python Livre avec un constructeur __init__ (titre, auteur, pages), une méthode resume() qui affiche un résumé court, et une méthode __str__ pour un affichage lisible. Crée deux instances et montre que chacune a son propre état.
💬 Ré-explique sans regarder
Sans relire le code de l'IA : avec tes mots, pourquoi deux instances créées avec Compte("Alice", 100) et Compte("Bob", 0) ont-elles chacune leur propre solde ? Quel rôle joue self là-dedans ?
Compte(...) crée un nouvel objet distinct, et __init__ écrit les attributs sur cet objet-là via self. self désigne l'instance en cours : self.solde = solde stocke le solde dans la mémoire propre d'Alice, pas dans une zone partagée. Du coup, modifier le solde de Bob ne touche jamais celui d'Alice : les deux self pointent vers deux objets différents.⚖️ Juge le code de l'IA
L'IA te propose cette classe Panier. Ton rôle de relecteur : l'accepter telle quelle ou la rejeter, et dire pourquoi.
class Panier:
def __init__(self, articles=[]):
self.articles = articles
def ajouter(self, article):
self.articles.append(article)
p1 = Panier()
p1.ajouter("pomme")
p2 = Panier()
print(p2.articles) # ['pomme'] ?!
[] de def __init__(self, articles=[]) est créée une seule fois, au moment où Python définit la fonction, puis partagée par toutes les instances qui ne fournissent pas d'articles. Résultat : p1 et p2 pointent vers la même liste, et ajouter une pomme à l'un la fait apparaître chez l'autre. Le correctif pro : def __init__(self, articles=None): puis self.articles = articles if articles is not None else [], pour créer une liste neuve à chaque objet.🧠 Rappel libre
Sans remonter dans la leçon : à quoi sert __init__, et que doit renvoyer __str__ ?
__init__ est le constructeur : Python l'appelle automatiquement à chaque création d'objet pour l'initialiser, et c'est là qu'on pose les attributs sur self (ex. self.titulaire = titulaire). __str__ doit renvoyer une chaîne (pas l'afficher avec print) ; cette chaîne est celle que print(objet) et str(objet) utiliseront pour un affichage lisible.__init__ dans une classe ?self dans une méthode ?super().__init__(nom) dans class Chien(Animal) ?Vos objets vivent en mémoire, mais ils disparaissent dès que le programme se ferme. Pour garder les données, on apprend à lire et écrire des fichiers, puis à organiser son code en modules et à installer des packages avec pip.
Leçon 7 : Fichiers et modules →