Essayer de coder ← Retour aux sujets

Classes et objets

POO

Regrouper l'état (les attributs) et le comportement (les méthodes) dans un type que l'on définit soi-même.

class Point:
Déclarer une classe : un modèle d'objets
def __init__(self, x):
Constructeur : initialise chaque nouvel objet
self.x = x
Attribut d'instance : une donnée propre à l'objet
def distance(self): ...
Méthode : une fonction qui reçoit l'objet (self)
p = Point(3)
Créer un objet (une instance)
p.x p.distance()
Lire un attribut, appeler une méthode
compte.py
class Compte:
    def __init__(self, solde=0):
        self.solde = solde

    def crediter(self, montant):
        self.solde += montant

c = Compte(100)
c.crediter(50)
print(c.solde)
Affichage
150
💡 Héritage

super() permet de réutiliser le comportement de la classe mère, dans __init__ ou dans n'importe quelle méthode.

Héritage et méthodes spéciales

aller plus loin

Une classe fille hérite de sa mère et peut redéfinir ses méthodes ; les méthodes spéciales (__str__, __eq__…) font fonctionner les objets avec print, ==, +, len…

class Chien(Animal):
Hériter d'une classe
super().__init__(nom)
Appeler le constructeur de la classe mère
def __str__(self):
Texte affiché par print(objet)
def __eq__(self, autre):
Définir ce que veut dire a == b
@property
Un attribut calculé ou contrôlé
isinstance(obj, Classe)
L'objet est-il de cette classe (ou d'une fille) ?
heritage_court.py
class Animal:
    def parler(self):
        return "..."

class Chien(Animal):
    def parler(self):
        return "Ouaf"

print(Chien().parler(), isinstance(Chien(), Animal))
Affichage
Ouaf True

🏛️ Le cours : classes et objets

Une classe est un modèle qui décrit un genre d'objets : les attributs (les données que chaque objet porte) et les méthodes (ce qu'il sait faire). Un objet, ou instance, est un exemplaire fabriqué à partir de la classe. En Python, tout est objet : un entier, un texte, une liste ont des méthodes ("nsi".upper(), liste.append(1)) ; les classes permettent de créer ses propres types.

MotSignificationExemple
classele modèlePersonne
instance (objet)un exemplaire de la classeeleve = Personne("Ada", 17)
attributune donnée stockée dans l'objetself.nom
méthodeune fonction définie dans la classe, qui agit sur l'objetdef se_presenter(self):
selfl'objet lui-même, premier paramètre de toute méthodeself.age += 1
constructeurla méthode __init__, appelée à la créationPersonne("Ada", 17)

Définir une classe et créer des objets

On définit une classe avec class Nom: (par convention en CamelCase). La méthode spéciale __init__ est appelée automatiquement quand on crée un objet : elle y range les valeurs de départ dans des attributs self.nom = .... Chaque méthode reçoit l'objet en premier paramètre, nommé self ; à l'appel, on ne le passe pas : eleve.se_presenter() envoie eleve comme self. Chaque objet a ses propres attributs.

personne.py
class Personne:
    def __init__(self, nom, age):
        self.nom = nom
        self.age = age

    def se_presenter(self):
        return f"Je m'appelle {self.nom} et j'ai {self.age} ans."

eleve = Personne("Ada", 17)
prof = Personne("Lin", 40)
print(eleve.se_presenter())
print(prof.nom, prof.age)
eleve.age += 1
print(eleve.age, prof.age)
Affichage
Je m'appelle Ada et j'ai 17 ans.
Lin 40
18 40

Des méthodes qui agissent sur l'objet

Une méthode lit et modifie les attributs de l'objet grâce à self. Elle peut renvoyer une valeur (comme debiter, qui dit si l'opération a réussi) ou simplement modifier l'état (comme crediter).

methodes.py
class Compte:
    def __init__(self, solde=0):
        self.solde = solde

    def crediter(self, montant):
        self.solde += montant

    def debiter(self, montant):
        if montant > self.solde:
            return False
        self.solde -= montant
        return True

c = Compte(100)
c.crediter(50)
print(c.debiter(500), c.solde)
print(c.debiter(120), c.solde)
Affichage
False 150
True 30

Attributs de classe et attributs d'instance

Un attribut défini dans la classe, hors de toute méthode, est un attribut de classe : il est partagé par tous les objets (compteur d'instances, constante). Un attribut défini avec self. est propre à chaque objet. On modifie l'attribut de classe en passant par le nom de la classe.

attribut_classe.py
class Compte:
    nb_comptes = 0

    def __init__(self, solde=0):
        self.solde = solde
        Compte.nb_comptes += 1

c1 = Compte(100)
c2 = Compte(50)
print(c1.solde, c2.solde)
print(Compte.nb_comptes, c1.nb_comptes)
Affichage
100 50
2 2

Afficher un objet : __str__ et __repr__

Sans précaution, print(objet) affiche un texte peu parlant (du genre <... object at 0x7f...>). On définit __str__ pour le texte destiné à l'utilisateur (utilisé par print et str) et __repr__ pour une représentation de développeur, plus précise (utilisée dans les listes et la console).

str_repr.py
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

p = Point(3, 4)
print(p)
print([p, Point(0, 1)])
print(repr(p), str(p))
Affichage
(3, 4)
[Point(3, 4), Point(0, 1)]
Point(3, 4) (3, 4)

Encapsulation : protéger l'état d'un objet

Pour éviter qu'on modifie n'importe comment les attributs d'un objet, on adopte des conventions : un nom qui commence par _ signale un attribut interne (à ne pas toucher de l'extérieur) et __ déclenche en plus une transformation du nom (*name mangling*). Python n'impose rien de strict : c'est une convention de politesse. Pour contrôler la lecture et l'écriture d'un attribut, on utilise @property (lecture) et @nom.setter (écriture) : le code appelant écrit c.solde = 150 comme avec un attribut ordinaire, mais une vérification s'exécute en coulisses.

property.py
class CompteBancaire:
    def __init__(self, solde=0):
        self._solde = solde

    @property
    def solde(self):
        return self._solde

    @solde.setter
    def solde(self, valeur):
        if valeur < 0:
            raise ValueError("solde négatif interdit")
        self._solde = valeur

c = CompteBancaire(100)
c.solde = 150
print(c.solde)
try:
    c.solde = -5
except ValueError as e:
    print("Erreur :", e)
Affichage
150
Erreur : solde négatif interdit
mangling.py
class Coffre:
    def __init__(self):
        self.__code = 1234

c = Coffre()
print(hasattr(c, "__code"), hasattr(c, "_Coffre__code"))
Affichage
False True

Le nom __code est transformé en _Coffre__code : on évite les collisions entre classes, mais on n'obtient pas une vraie confidentialité.

Méthodes de classe et méthodes statiques

@classmethod définit une méthode qui reçoit la classe (cls) au lieu de l'objet : elle sert souvent de constructeur alternatif. @staticmethod définit une fonction rangée dans la classe qui ne reçoit ni objet ni classe : un simple outil lié au sujet de la classe.

classmethod.py
class Cercle:
    PI = 3.14159

    def __init__(self, rayon):
        self.rayon = rayon

    @classmethod
    def depuis_diametre(cls, diametre):
        return cls(diametre / 2)

    @staticmethod
    def est_valide(rayon):
        return rayon > 0

    def aire(self):
        return Cercle.PI * self.rayon ** 2

c = Cercle.depuis_diametre(10)
print(c.rayon, round(c.aire(), 2), Cercle.est_valide(-1))
Affichage
5.0 78.54 False

L'héritage et super()

Une classe peut hériter d'une autre : class Chien(Animal): donne à Chien tous les attributs et méthodes d'Animal. La classe fille peut redéfinir (*override*) une méthode, et appeler celle de la mère avec super(). Si deux classes filles définissent la même méthode différemment, un même appel a.parler() donne un résultat adapté à chaque objet : c'est le polymorphisme. isinstance(obj, Classe) est vrai pour la classe de l'objet et ses classes mères.

heritage.py
class Animal:
    def __init__(self, nom):
        self.nom = nom

    def parler(self):
        return f"{self.nom} fait un bruit."

class Chien(Animal):
    def __init__(self, nom, race):
        super().__init__(nom)
        self.race = race

    def parler(self):
        return f"{self.nom} aboie !"

class Chat(Animal):
    pass

rex = Chien("Rex", "Labrador")
felix = Chat("Felix")
for a in [rex, felix]:
    print(a.parler())
print(isinstance(rex, Animal), isinstance(rex, Chat), issubclass(Chien, Animal))
print([c.__name__ for c in Chien.__mro__])
Affichage
Rex aboie !
Felix fait un bruit.
True False True
['Chien', 'Animal', 'object']

Chien.__mro__ donne l'ordre dans lequel Python cherche une méthode : la classe elle-même, ses mères, puis object, ancêtre de toutes les classes.

Classes abstraites

Une classe peut décrire un contrat sans fournir le code : avec le module abc, une méthode marquée @abstractmethod doit obligatoirement être définie par les classes filles, et on ne peut pas créer d'objet de la classe abstraite elle-même.

abstraite.py
from abc import ABC, abstractmethod

class Forme(ABC):
    @abstractmethod
    def aire(self):
        ...

class Carre(Forme):
    def __init__(self, cote):
        self.cote = cote

    def aire(self):
        return self.cote ** 2

print(Carre(3).aire())
try:
    Forme()
except TypeError:
    print("Forme est abstraite")
Affichage
9
Forme est abstraite

Méthodes spéciales : faire vivre ses objets avec les opérateurs

Les méthodes dont le nom est entouré de deux tirets bas (« dunder » : *double underscore*) sont appelées par Python dans des situations précises : __add__ pour a + b, __eq__ pour a == b, __len__ pour len(a), __getitem__ pour a[i], __lt__ pour a < b… En les définissant, on rend ses objets aussi pratiques que les types intégrés.

operateurs.py
class Vecteur:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, autre):
        return Vecteur(self.x + autre.x, self.y + autre.y)

    def __eq__(self, autre):
        return self.x == autre.x and self.y == autre.y

    def __len__(self):
        return 2

    def __getitem__(self, i):
        return (self.x, self.y)[i]

    def __repr__(self):
        return f"Vecteur({self.x}, {self.y})"

u = Vecteur(1, 2)
v = Vecteur(3, 4)
print(u + v, u == Vecteur(1, 2), u == v)
print(len(u), u[1], list(u))
Affichage
Vecteur(4, 6) True False
2 2 [1, 2]

Définir __lt__ suffit pour que sorted, min et max fonctionnent sur ses objets.

tri_objets.py
class Eleve:
    def __init__(self, nom, note):
        self.nom = nom
        self.note = note

    def __lt__(self, autre):
        return self.note < autre.note

    def __repr__(self):
        return f"{self.nom}({self.note})"

classe = [Eleve("Ada", 18), Eleve("Lin", 15), Eleve("Max", 17)]
print(sorted(classe))
print(max(classe))
Affichage
[Lin(15), Max(17), Ada(18)]
Ada(18)

dataclass : une classe de données en trois lignes

Pour une classe qui ne fait que porter des données, le décorateur @dataclass (module dataclasses) écrit à ta place __init__, __repr__ et __eq__ à partir des attributs annotés.

dataclass.py
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int = 0

p = Point(3)
print(p, p == Point(3, 0), p.x)
Affichage
Point(x=3, y=0) True 3

Composition ou héritage ?

L'héritage exprime « est un » (un Chien est un Animal). Quand un objet contient un autre, on parle de composition (« a un ») : une Voiture a un Moteur. Dans le doute, préfère la composition : elle est plus souple et évite des hiérarchies de classes compliquées.

composition.py
class Moteur:
    def __init__(self, puissance):
        self.puissance = puissance

class Voiture:
    def __init__(self, marque, puissance):
        self.marque = marque
        self.moteur = Moteur(puissance)

v = Voiture("Renault", 90)
print(v.marque, v.moteur.puissance)
Affichage
Renault 90

⚠️ Erreurs fréquentes

Oublier self dans la définition d'une méthode

Python passe automatiquement l'objet en premier argument : si la méthode ne prévoit pas de paramètre self, l'appel échoue avec un message qui parle d'un argument « de trop ».

À éviter
faux.py
class Chat:
    def miauler():
        return "miaou"

print(Chat().miauler())
Erreur
TypeError: Chat.miauler() takes 0 positional arguments but 1 was given
Correct
correct.py
class Chat:
    def miauler(self):
        return "miaou"

print(Chat().miauler())
Affichage
miaou

Oublier self. devant un attribut

Dans __init__, solde = solde crée une simple variable locale qui disparaît à la fin de la méthode : pour créer un attribut de l'objet, il faut self.solde = solde.

À éviter
faux.py
class Compte:
    def __init__(self, solde):
        solde = solde

    def afficher(self):
        return self.solde

print(Compte(10).afficher())
Erreur
AttributeError: 'Compte' object has no attribute 'solde'
Correct
correct.py
class Compte:
    def __init__(self, solde):
        self.solde = solde

    def afficher(self):
        return self.solde

print(Compte(10).afficher())
Affichage
10

Une liste en attribut de classe

Un attribut de classe est partagé par tous les objets : une liste déclarée dans le corps de la classe est modifiée par chacun. Crée les attributs modifiables dans __init__, avec self..

À éviter
faux.py
class Panier:
    articles = []

    def ajouter(self, x):
        self.articles.append(x)

a = Panier()
b = Panier()
a.ajouter("pomme")
print(b.articles)
Affichage
['pomme']
Correct
correct.py
class Panier:
    def __init__(self):
        self.articles = []

    def ajouter(self, x):
        self.articles.append(x)

a = Panier()
b = Panier()
a.ajouter("pomme")
print(b.articles)
Affichage
[]

Comparer deux objets sans __eq__

Par défaut, == compare l'identité (est-ce le même objet ?) et non le contenu. Deux points de mêmes coordonnées sont donc différents tant qu'on ne définit pas __eq__.

À éviter
faux.py
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

print(Point(1, 2) == Point(1, 2))
Affichage
False
Correct
correct.py
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, autre):
        return self.x == autre.x and self.y == autre.y

print(Point(1, 2) == Point(1, 2))
Affichage
True

Oublier d'appeler super().__init__

Si la classe fille définit son propre __init__, celui de la mère n'est pas appelé automatiquement : ses attributs n'existent pas.

À éviter
faux.py
class Animal:
    def __init__(self, nom):
        self.nom = nom

class Chien(Animal):
    def __init__(self, race):
        self.race = race

print(Chien("Labrador").nom)
Erreur
AttributeError: 'Chien' object has no attribute 'nom'
Correct
correct.py
class Animal:
    def __init__(self, nom):
        self.nom = nom

class Chien(Animal):
    def __init__(self, nom, race):
        super().__init__(nom)
        self.race = race

print(Chien("Rex", "Labrador").nom)
Affichage
Rex

Appeler une méthode sur la classe au lieu d'un objet

À éviter
faux.py
class Chat:
    def miauler(self):
        return "miaou"

print(Chat.miauler())
Erreur
TypeError: Chat.miauler() missing 1 required positional argument: 'self'
Correct
correct.py
class Chat:
    def miauler(self):
        return "miaou"

print(Chat().miauler())
Affichage
miaou

Modifier un attribut de classe depuis une instance

Écrire objet.total = 5 ne change pas l'attribut de classe : cela crée un attribut d'instance qui le masque. Pour modifier la valeur partagée, passe par le nom de la classe.

masquage.py
class Compteur:
    total = 0

a = Compteur()
a.total = 5
print(a.total, Compteur.total)
Compteur.total = 7
print(a.total, Compteur.total, Compteur().total)
Affichage
5 0
5 7 7

📋 Aide-mémoire

Écrire une classe

SyntaxeRôle
class Nom: class Fille(Mere):définir une classe ; hériter d'une classe (Fille(A, B) : héritage multiple)
def __init__(self, ...):constructeur ; y créer les attributs avec self.attribut = ...
def methode(self, ...):méthode d'instance
attribut = valeur dans le corps de la classeattribut de classe, partagé (Classe.attribut)
@classmethod + def f(cls, ...):méthode qui reçoit la classe (constructeur alternatif)
@staticmethod + def f(...):fonction rangée dans la classe, sans self ni cls
@property @nom.setterlecture et écriture contrôlées d'un attribut (_nom en interne)
super().__init__(...) super().methode()appeler la version de la classe mère
_attr __attrattribut interne par convention ; attribut au nom transformé (*name mangling*)
@dataclass (module dataclasses)génère __init__, __repr__, __eq__ d'une classe de données
class Forme(ABC) + @abstractmethodclasse abstraite (module abc) : méthodes à définir par les filles
__slots__ = ["a", "b"]fixer la liste des attributs (économise de la mémoire)

Méthodes spéciales

CatégorieMéthodesDéclenchées par
Création et vie de l'objet__new__ __init__ __del__Classe(...), ramasse-miettes
Texte__str__ __repr__print(x), str(x) ; repr(x), console, listes
Comparaisons__eq__ __ne__ __lt__ __le__ __gt__ __ge__== != < <= > >=, sorted, max
Arithmétique__add__ __sub__ __mul__ __truediv__ __floordiv__ __mod__ __pow__+ - * / // % **
Conteneur__len__ __getitem__ __setitem__ __delitem__ __contains__len(x), x[i], x[i] = v, del x[i], v in x
Itération__iter__ __next__for e in x, next(x)
Conversions__bool__ __int__ __float__ __hash__bool(x), int(x), float(x), clé de dictionnaire ou d'ensemble
Appel__call__x(...)
Contexte__enter__ __exit__with x as y:

Interroger un objet

OutilRésultat
isinstance(obj, Classe) issubclass(Fille, Mere)appartenance à une classe (ou à une de ses filles) ; lien d'héritage
obj.__class__ Classe.__name__ Classe.__bases__ Classe.__mro__classe de l'objet, nom, classes mères, ordre de recherche des méthodes
obj.__dict__ vars(obj) dir(obj)dictionnaire des attributs ; liste des attributs et méthodes
getattr(obj, "a") setattr(obj, "a", v) hasattr(obj, "a") delattr(obj, "a")lire, écrire, tester, supprimer un attribut dont le nom est dans une variable
  • Chaque objet a ses attributs (self.x) ; un attribut de classe est partagé : ne mets jamais une liste ou un dictionnaire modifiable dans le corps de la classe.
  • == compare l'identité tant qu'on ne définit pas __eq__ ; print utilise __str__, les listes utilisent __repr__.
  • Une classe fille qui définit __init__ doit appeler super().__init__(...).
  • est un → héritage ; a un → composition (un attribut qui est un autre objet).