Le chat d'octets

Photo de Gribouille

La programmation Orientée Objet (POO)

La programmation orientée objet (POO) est une méthode de programmation qui consiste à structurer un programme en regroupant données et comportements dans des entités appelées objets.

En Python, cela se fait en définissant des classes, qui sont comme des modèles, et en créant des objets à partir de ces modèles. Chaque objet a des attributs (des données) et des méthodes (des fonctions).

Structure de base en Python


class Chat:
    espece = "Felis catus"          # Attribut de classe
    def __init__(self, nom, age):
        self.nom = nom              # Attribut d'instance
        self.age = age

    def miauler(self):
        print(f"{self.nom} miaule !")

gribouille = Chat("Gribouille", 16)
gribouille.miauler()
print(Chat.espece)    # Accès à un attribut de classe 

Résultat : Gribouille miaule !

Ici, Chat est une classe avec un constructeur __init__ qui initialise deux attributs (nom et race) et une méthode miauler().

Les noms de classes suivent la convention du CamelCase, un style d’écriture où les mots sont accolés sans espaces et chaque mot commence par une majuscule (ex. : MaClasse).

En Python, self est une référence à l’instance d’une classe : il permet d’accéder aux attributs et méthodes de l’objet depuis l’intérieur de la classe. Il doit être le premier paramètre de toutes les méthodes d’instance.

self.nom = nom : Crée un attribut appelé nom et lui attribut la valeur du paramètre nom.

Les attributs de classe sont des variables définies directement dans le corps de la classe. Ils sont partagés par toutes les instances, ce qui signifie qu’une modification de leur valeur au niveau de la classe affecte toutes les instances. À l’inverse, les attributs d’instance sont définis dans le constructeur (__init__) et sont propres à chaque objet. Ainsi, chaque instance peut posséder ses propres valeurs indépendantes.

Les attributs de classe sont définis directement dans le corps de la classe, en dehors de toute méthode, et doivent être initialisés dès leur déclaration.

Les méthodes dans une classe Python

Dans une classe Python, plusieurs types de méthodes existent pour organiser le comportement des objets. Chaque méthode a un rôle précis selon qu’elle agit sur une instance, la classe, ou qu’elle est indépendante des deux.

Méthodes d’instance

Les méthodes d’instance sont les plus courantes. Elles sont définies pour agir sur une instance particulière de la classe. Ces méthodes reçoivent toujours en premier argument self, qui représente l’objet lui-même. Grâce à self, elles peuvent accéder et modifier les attributs propres à cette instance. Par exemple, dans la classe Chat, la méthode miauler utilise self.nom pour accéder au nom spécifique du chat qui appelle la méthode.


class Chat:
    def __init__(self, nom):
        self.nom = nom  # attribut d’instance

    def miauler(self):
        print(f"{self.nom} miaule.")
    

Méthodes de classe

Les méthodes de classe ne concernent pas une instance particulière, mais la classe dans son ensemble. Elles reçoivent en premier argument cls, qui représente la classe elle-même. Pour les définir, on utilise le décorateur @classmethod. Ces méthodes permettent d’accéder ou de modifier des attributs de classe, ou encore de créer des objets d’une autre manière. Par exemple, dans la classe Chat, une méthode de classe peut modifier un attribut commun à tous les chats, comme espece.


class Chat:
    espece = "Felis catus"  # attribut de classe

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

    @classmethod
    def changer_espece(cls, nouvelle_espece):
        cls.espece = nouvelle_espece

Méthodes statiques

Les méthodes statiques sont des fonctions définies dans la classe qui ne dépendent ni de l’instance, ni de la classe. Elles ne reçoivent donc ni self ni cls en argument. Pour les créer, on utilise le décorateur @staticmethod. Ces méthodes sont utiles pour regrouper des fonctions liées logiquement à la classe, sans qu’elles aient besoin d’accéder aux données internes. Par exemple, une méthode statique dans la classe Chat peut simplement afficher une information générale sur les chats.


class Chat:
    @staticmethod
    def information():
        print("Les chats sont des animaux domestiques très populaires.")

La méthode information peut être appelée directement via la classe, sans qu’il soit nécessaire de créer une instance.

Les 4 grands principes de la POO

La Programmation Orientée Objet (POO) est un paradigme de programmation qui repose sur la modélisation du code sous forme d’objets. Ces objets sont construits à partir de classes, qui définissent leur structure (attributs) et leur comportement (méthodes). La POO vise à rendre le code plus structuré, réutilisable, modulaire et facile à maintenir. Elle repose principalement sur quatre grands principes fondamentaux : l'encapsulation, l'abstraction, l'héritage et le polymorphisme.

1. Encapsulation

L’encapsulation consiste à cacher les détails internes d’un objet pour protéger les données et contrôler les accès. En Python, on utilise des attributs "privés" (par convention, en les préfixant avec un underscore).


class CompteBancaire:
    def __init__(self, titulaire, solde_initial):
        self.titulaire = titulaire
        self._solde = solde_initial  # Attribut "protégé"

    def deposer(self, montant):
        self._solde += montant

    def retirer(self, montant):
        if montant <= self._solde:
            self._solde -= montant

    def afficher_solde(self):
        print(f"Solde de {self.titulaire} : {self._solde} €")

compte = CompteBancaire("Halloween", 1000)
compte.afficher_solde()

L’utilisateur du compte peut utiliser les méthodes pour interagir avec l’objet, sans modifier directement le solde. Cela renforce la sécurité du code.

Remarque : Même si l’attribut _solde commence par un underscore, on peut toujours y accéder directement depuis l’extérieur comme ceci :


compte = CompteBancaire("Halloween", 1000)
compte._solde = 999999  # Modifie directement l'attribut

compte.afficher_solde()  # Affichera : Solde de Halloween : 999999 €

Cela fonctionne, mais ne respecte pas le principe d’encapsulation. En Python, le underscore sert de signal aux développeurs : "ne touchez pas directement à cette variable". Il ne bloque pas techniquement l'accès.

Encapsulation avancée avec __ (attributs privés)

En Python, on peut rendre un attribut "privé" en le commençant par deux underscores __. Cela déclenche un mécanisme interne appelé name mangling : Python renomme automatiquement l’attribut en _NomDeLaClasse__nomDeLAttribut.


class CompteBancaire:
    def __init__(self, titulaire, solde_initial):
        self.titulaire = titulaire
        self.__solde = solde_initial  # Attribut privé

    def deposer(self, montant):
        self.__solde += montant

    def retirer(self, montant):
        if montant <= self.__solde:
            self.__solde -= montant

    def afficher_solde(self):
        print(f"Solde de {self.titulaire} : {self.__solde} €")

compte = CompteBancaire("Halloween", 1000)
compte.afficher_solde()
compte.__solde = 999999  # Tentative d'accès direct : échoue
compte.afficher_solde()  # Le solde reste inchangé

Même si on essaie de modifier __solde directement, Python le protège car le vrai nom interne est en fait _Compte__solde.

Cela permet d’éviter que des parties du programme accèdent ou modifient accidentellement des données sensibles.

2. Héritage

L’héritage permet de créer une nouvelle classe à partir d’une classe existante. On réutilise le code de la classe "parente" et on peut le compléter ou le modifier dans la classe "enfant".


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

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

class Chien(Animal):
    def parler(self):
        print(f"{self.nom} aboie.")

class Chat(Animal):
    def parler(self):
        print(f"{self.nom} miaule.")

Ici, Chien et Chat héritent de la classe Animal, mais redéfinissent la méthode parler().

Utilisation de super()

La fonction super() est utilisée dans une classe enfant pour appeler une méthode définie dans sa classe parente. Elle est souvent utilisée dans le constructeur __init__() afin de ne pas dupliquer l’initialisation des attributs déjà présents dans la classe mère.

Cela permet à la classe fille de compléter ou étendre le comportement de la classe mère tout en conservant sa logique.


class Animal:
    def __init__(self, nom):
        self.nom = nom
        print(f"L'animal {self.nom} est créé.")

class Chat(Animal):
    def __init__(self, nom, age):
        super().__init__(nom)  # Appelle le constructeur de Animal
        self.age = age
        print(f"C'est un chat de {self.age} ans.")

lancelot = Chat("Gribouille", 16)

Sortie :


L'animal Gribouille est créé.
C'est un chat de 16 ans.

Ici, la classe Chat hérite de Animal. Grâce à super(), elle appelle le constructeur de Animal pour initialiser le nom, puis ajoute ses propres attributs.

Pourquoi utiliser super() ?

super() est donc une bonne pratique en programmation orientée objet, surtout dans des hiérarchies complexes où plusieurs classes collaborent. Elle permet d’assurer une logique cohérente tout en évitant la redondance.

On peut également utiliser super() dans d’autres méthodes que __init__, par exemple pour appeler une méthode d’affichage ou de traitement définie dans la classe parente.


class Animal:
    def parler(self):
        print("L'animal fait un bruit.")

class Chat(Animal):
    def parler(self):
        super().parler()  # Appelle Animal.parler()
        print("Le chat miaule.")

chat = Chat()

Résultat :


L'animal fait un bruit.
Le chat miaule.

3. Polymorphisme

Le polymorphisme permet d’utiliser le même nom de méthode pour différents comportements selon l’objet. Cela rend le code plus flexible.


animaux = [Chien("Rex"), Chat("Gribouille")]

for animal in animaux:
    animal.parler()

Chaque objet appelle la bonne version de parler() selon sa classe. Le même code fonctionne pour plusieurs types d'objets.

4. Abstraction

L’abstraction consiste à masquer la complexité et ne montrer que ce qui est utile. On peut créer des classes "abstraites" avec des méthodes que les sous-classes doivent implémenter.

Python propose le module abc pour définir des classes abstraites.


from abc import ABC, abstractmethod

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

class Cercle(Forme):
    def __init__(self, rayon):
        self.rayon = rayon

    def aire(self):
        return 3.14 * self.rayon ** 2

La classe Forme est abstraite et ne peut pas être instanciée directement. Toute classe qui hérite de Forme doit obligatoirement définir la méthode aire().

Pourquoi utiliser la POO ?

En résumé