
La programmation orientée objet (POO) en Python consiste à regrouper des données (les attributs) et des comportements (les méthodes) dans des objets, créés à partir d'un modèle appelé classe. Au lieu d'éparpiller variables et fonctions, vous décrivez une fois une classe (par exemple Voiture), puis vous fabriquez autant d'objets que nécessaire, chacun avec ses propres caractéristiques.
Python est d'ailleurs un langage objet de bout en bout : les chaînes, les listes et les dictionnaires que vous utilisez déjà sont des objets. Ce guide, pour développeurs débutants et intermédiaires, part de la première classe jusqu'aux méthodes spéciales, avec des exemples concrets d'administration système.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Définir une classe et instancier un objet avec
__init__etself - Distinguer attributs et méthodes, attributs de classe et d'instance
- Protéger l'état avec l'encapsulation (
__) - Réutiliser du code avec l'héritage et
super() - Adapter le comportement avec le polymorphisme
- Exploiter les méthodes spéciales (
__str__,__eq__,__len__,__call__)
Introduction à la POO
Section intitulée « Introduction à la POO »La programmation orientée objet, ou "POO", c’est une façon de programmer en imaginant que tout ce qu’on manipule dans un programme est comme un objet de la vie réelle.
Imagine... : Tu veux créer un jeu vidéo où il y a des voitures. Plutôt que de tout écrire pour chaque voiture, tu commences par créer un modèle ou une recette qui décrit ce qu'est une voiture.
Dans ta recette de voiture, tu peux dire :
- Une voiture a des caractéristiques comme une couleur, une marque, ou une vitesse.
- Une voiture peut faire des choses comme rouler, klaxonner, ou freiner.
class Voiture: def __init__(self, couleur, marque): self.couleur = couleur self.marque = marque self.vitesse = 0
def rouler(self): self.vitesse += 10 print(f"La {self.marque} roule à {self.vitesse} km/h.")
def freiner(self): self.vitesse = max(0, self.vitesse - 10) print(f"La {self.marque} freine et roule à {self.vitesse} km/h.")Ensuite, tu peux fabriquer plein de voitures à partir de ta recette.
Ces voitures, on les appelle des objets.
ma_voiture = Voiture("rouge", "Ferrari")ta_voiture = Voiture("bleue", "Renault")
ma_voiture.rouler() # La Ferrari roule à 10 km/h.ta_voiture.rouler() # La Renault roule à 10 km/h.Chaque voiture est indépendante, mais elles utilisent la même recette pour exister.
Les avantages :
- Organisation : Tu ranges tout ce qui concerne les voitures au même endroit.
- Réutilisation : Une seule recette peut créer des milliers d’objets.
- Évolution : Si tu veux ajouter un toit ouvrant à toutes les voitures, tu modifies juste la recette.
C’est comme jouer avec des LEGO : tu crées des pièces (classes) et après tu
peux construire tout ce que tu veux (objets) ! Imaginez devoir gérer une
flotte de véhicules dans un programme. Sans la POO, vous auriez une
multitude de variables et de fonctions éparpillées. Avec la programmation
orientée objet, vous pouvez créer une classe Véhicule et ensuite instancier
autant d'objets Véhicule que nécessaire, chacun avec ses propres
caractéristiques.
Python supporte pleinement la programmation orientée objet. Même si vous n'écrivez pas explicitement de classes, tout est objet en Python. Les chaînes de caractères, les listes, les dictionnaires sont tous des objets avec des méthodes que vous utilisez déjà au quotidien.
Les Concepts de Base de la POO
Section intitulée « Les Concepts de Base de la POO »Plongeons dans le vif du sujet ! Quels sont les éléments clés de la programmation orientée objet en Python ?
Les classes et les objets
Section intitulée « Les classes et les objets »Une classe est comme un plan ou un moule qui définit les attributs et les méthodes communes à tous les objets de ce type. Un objet est une instance de cette classe, une réalisation concrète.
Par exemple :
class Personne: pass
individu = Personne()Ici, Personne est une classe et individu est un objet (ou instance) de
cette classe.
Les attributs et les méthodes
Section intitulée « Les attributs et les méthodes »Les attributs sont les données qui décrivent l'état de l'objet, tandis que les méthodes sont les fonctions qui définissent son comportement.
class Personne: def __init__(self, nom, âge): self.nom = nom # Attribut self.âge = âge # Attribut
def se_presenter(self): # Méthode print(f"Bonjour, je m'appelle {self.nom} et j'ai {self.âge} ans.")L'instanciation d'un objet
Section intitulée « L'instanciation d'un objet »Pour créer un objet à partir d'une classe, on utilise la syntaxe suivante :
moi = Personne("Alice", 30)moi.se_presenter() # Affiche "Bonjour, je m'appelle Alice et j'ai 30 ans."L'encapsulation
Section intitulée « L'encapsulation »L'encapsulation consiste à protéger les attributs et méthodes internes d'une
classe en limitant leur accès depuis l'extérieur. En Python, on peut indiquer
qu'un attribut est privé en le précédant de deux underscores __.
class CompteBancaire: def __init__(self, solde): self.__solde = solde # Attribut privé
def deposer(self, montant): self.__solde += montant
def retirer(self, montant): if montant <= self.__solde: self.__solde -= montant else: print("Fonds insuffisants")
def afficher_solde(self): print(f"Le solde est de {self.__solde} euros.")Essayer d'accéder directement à __solde depuis l'extérieur ne fonctionnera pas
:
compte = CompteBancaire(1000)print(compte.__solde) # Provoque une erreur d'attributLe polymorphisme
Section intitulée « Le polymorphisme »Le polymorphisme permet d'utiliser une même interface pour différents objets, ce qui rend le code plus flexible.
class Animal: def parler(self): pass
class Chien(Animal): def parler(self): print("Wouf!")
class Chat(Animal): def parler(self): print("Miaou!")
def faire_parler(animal): animal.parler()
faire_parler(Chien()) # Affiche "Wouf!"faire_parler(Chat()) # Affiche "Miaou!"L'héritage
Section intitulée « L'héritage »L'héritage permet à une classe enfant d'hériter des attributs et méthodes d'une classe parent, favorisant la réutilisation du code.
class Vehicule: def __init__(self, marque): self.marque = marque
def se_deplacer(self): print(f"Le véhicule {self.marque} se déplace.")
class Voiture(Vehicule): def __init__(self, marque, modèle): super().__init__(marque) self.modèle = modèle
def se_deplacer(self): print(f"La voiture {self.marque} {self.modèle} roule sur la route.")
ma_voiture = Voiture("Tesla", "Model S")ma_voiture.se_deplacer() # Affiche "La voiture Tesla Model S roule sur la route."Dans cet exemple, Voiture hérite de Vehicule et surcharge la méthode
se_deplacer().
Les attributs de classe et d'instance
Section intitulée « Les attributs de classe et d'instance »Les attributs de classe sont partagés entre toutes les instances, tandis que les attributs d'instance sont propres à chaque objet.
class Compte: banque = "Banque Nationale" # Attribut de classe
def __init__(self, titulaire): self.titulaire = titulaire # Attribut d'instance
compte1 = Compte("Alice")compte2 = Compte("Bob")
print(compte1.banque) # Affiche "Banque Nationale"print(compte2.banque) # Affiche "Banque Nationale"Créer sa Première Classe en Python
Section intitulée « Créer sa Première Classe en Python »Passons à la pratique ! Quoi de mieux que de créer une classe qui nous sera réellement utile en tant que personne en charge d'automatiser des procédures ?
Imaginez que vous devez gérer plusieurs services sur vos serveurs, comme Nginx, Apache ou Docker. À mon avis, avoir une classe qui représente un service et permet de contrôler son état serait un véritable gain de temps.
Voici comment définir une classe en Python :
class Service: pass # La classe est vide et ne fait rienBon, ce n'est pas très passionnant pour le moment. Ajoutons quelques attributs et méthodes pour rendre cette classe utile.
La méthode __init__ est le constructeur de la classe. Elle initialise les
attributs de l'objet lors de sa création.
class Service: def __init__(self, nom): self.nom = nomAjoutons des méthodes pour démarrer, arrêter et redémarrer le service.
class Service: def __init__(self, nom): self.nom = nom
def demarrer(self): print(f"Démarrage du service {self.nom}") # Code pour démarrer le service
def arreter(self): print(f"Arrêt du service {self.nom}") # Code pour arrêter le service
def redemarrer(self): print(f"Redémarrage du service {self.nom}") # Code pour redémarrer le serviceCréons une instance de notre classe pour gérer Nginx.
nginx = Service("nginx")nginx.demarrer() # Affiche "Démarrage du service nginx"nginx.arreter() # Affiche "Arrêt du service nginx"nginx.redemarrer() # Affiche "Redémarrage du service nginx"Pour rendre notre classe fonctionnelle, utilisons le module subprocess pour
exécuter des commandes système.
import subprocess
class Service: def __init__(self, nom): self.nom = nom
def demarrer(self): print(f"Démarrage du service {self.nom}") subprocess.run(["sudo", "systemctl", "start", self.nom])
def arreter(self): print(f"Arrêt du service {self.nom}") subprocess.run(["sudo", "systemctl", "stop", self.nom])
def redemarrer(self): print(f"Redémarrage du service {self.nom}") subprocess.run(["sudo", "systemctl", "restart", self.nom])Ajoutons une méthode pour vérifier si le service est actif.
def statut(self): result = subprocess.run(["systemctl", "is-active", self.nom], capture_output=True, text=True) if result.stdout.strip() == "active": print(f"Le service {self.nom} est actif.") return True else: print(f"Le service {self.nom} n'est pas actif.") return FalseImaginons que nous devons gérer plusieurs services simultanément.
services = ["nginx", "docker", "mysql"]for nom_service in services: service = Service(nom_service) if not service.statut(): service.demarrer()Ce script vérifie si chaque service est actif et le démarre si nécessaire.
À mon avis, la programmation orientée objet rend le code plus modulable et réutilisable. En créant une classe générique pour les services, on évite de répéter du code et on facilite la maintenance. De plus, si demain on doit ajouter de nouvelles fonctionnalités, il suffit de les implémenter une fois dans la classe.
Il est important de gérer les erreurs potentielles, par exemple si le service n'existe pas.
def demarrer(self): print(f"Démarrage du service {self.nom}") try: subprocess.run(["sudo", "systemctl", "start", self.nom], check=True) except subprocess.CalledProcessError: print(f"Erreur : Le service {self.nom} n'a pas pu être démarré.")En tant qu'AdminSys, l'automatisation est notre meilleur ami. En utilisant la POO en Python, nous pouvons créer des outils puissants pour simplifier nos tâches quotidiennes.
Les Méthodes Spéciales
Section intitulée « Les Méthodes Spéciales »Vous avez peut-être déjà entendu parler des méthodes spéciales en Python, aussi connues sous le nom de dunder methods (pour double underscore). À mon avis, ces méthodes apportent une touche de magie à nos classes en nous permettant de définir le comportement des objets avec les fonctionnalités intégrées de Python.
Les méthodes spéciales sont des fonctions prédéfinies par Python qui ont des
noms entourés de doubles underscores, comme __init__, __str__, __repr__,
__add__, etc. Elles permettent à nos objets d'interagir avec les opérateurs et
les fonctions intégrées du langage.
Rappelez-vous de notre classe Service que nous avions créée pour gérer les
services sur nos serveurs. Et si nous utilisions des méthodes spéciales pour
rendre cette classe encore plus puissante et intuitive ?
La Méthode __str__
Section intitulée « La Méthode __str__ »La méthode __str__ définit comment un objet est représenté sous forme de
chaîne de caractères, notamment lorsqu'on utilise la fonction print().
class Service: def __init__(self, nom): self.nom = nom
def __str__(self): return f"Service: {self.nom}"nginx = Service("nginx")print(nginx) # Affiche "Service: nginx"La Méthode __eq__
Section intitulée « La Méthode __eq__ »Maintenant, chaque fois que nous imprimons un objet Service, nous obtenons une
description claire et lisible.
Si nous voulons comparer deux services pour savoir s'ils sont identiques, nous
pouvons utiliser la méthode __eq__.
def __eq__(self, autre): return self.nom == autre.nomservice1 = Service("nginx")service2 = Service("nginx")service3 = Service("docker")
print(service1 == service2) # Affiche Trueprint(service1 == service3) # Affiche FalseAinsi, l'opérateur == compare directement les noms des services.
La Méthode __iter__
Section intitulée « La Méthode __iter__ »Supposons que nous ayons une classe GestionnaireServices qui gère plusieurs
services. Nous pouvons la rendre itérable pour parcourir facilement les
services.
class GestionnaireServices: def __init__(self): self.services = [] self._index = 0
def ajouter_service(self, service): self.services.append(service)
def __iter__(self): self._index = 0 return self
def __next__(self): if self._index < len(self.services): service = self.services[self._index] self._index += 1 return service else: raise StopIterationgestionnaire = GestionnaireServices()gestionnaire.ajouter_service(Service("nginx"))gestionnaire.ajouter_service(Service("docker"))gestionnaire.ajouter_service(Service("mysql"))
for service in gestionnaire: print(service)Ce code affiche :
Service: nginxService: dockerService: mysqlLa Méthode __add__
Section intitulée « La Méthode __add__ »Nous pouvons définir comment additionner deux objets GestionnaireServices en
utilisant la méthode __add__.
def __add__(self, autre): nouveau_gestionnaire = GestionnaireServices() nouveau_gestionnaire.services = self.services + autre.services return nouveau_gestionnairegestionnaire1 = GestionnaireServices()gestionnaire1.ajouter_service(Service("nginx"))
gestionnaire2 = GestionnaireServices()gestionnaire2.ajouter_service(Service("docker"))
gestionnaire_combiné = gestionnaire1 + gestionnaire2
for service in gestionnaire_combiné: print(service)Affichage :
Service: nginxService: dockerNous avons ainsi combiné deux gestionnaires en un seul, grâce à l'opérateur +.
La Méthode __repr__
Section intitulée « La Méthode __repr__ »La méthode __repr__ fournit une représentation non ambiguë de l'objet, utile
pour le débogage.
def __repr__(self): return f"Service(nom='{self.nom}')"Exemple d'utilisation :
print(repr(nginx)) # Affiche "Service(nom='nginx')"La Méthode __del__
Section intitulée « La Méthode __del__ »Le destructeur est appelé lorsque l'objet est détruit. Parfois cela peut être utile pour libérer des ressources comme des fichiers ou des disques virtuels ouverts.
def __del__(self): print("Détruit le service", self.nom)La Méthode __call__
Section intitulée « La Méthode __call__ »Si nous souhaitons pouvoir appeler un objet comme une fonction, nous pouvons
utiliser la méthode __call__.
def __call__(self, action): if action == "démarrer": self.demarrer() elif action == "arrêter": self.arreter() elif action == "redémarrer": self.redemarrer()nginx("démarrer") # Démarre le service nginxnginx("redémarrer") # Redémarre le service nginxAutres méthodes spéciales utiles
Section intitulée « Autres méthodes spéciales utiles »__len__: Pour définir le comportement de la fonctionlen().__getitem__: Pour accéder aux éléments via l'indexation.__contains__: Pour vérifier l'appartenance avec l'opérateurin.
Exemple avec __len__ et __getitem__
Ajoutons ces méthodes à notre classe GestionnaireServices :
def __len__(self): return len(self.services)
def __getitem__(self, index): return self.services[index]Exemple d'utilisation :
print(len(gestionnaire)) # Affiche le nombre de servicesprint(gestionnaire[0]) # Affiche le premier serviceprint(Service("nginx") in gestionnaire) # Vérifie si nginx est dans le gestionnaireÀ retenir
Section intitulée « À retenir »- Une classe est un modèle ; un objet en est une instance créée avec
MaClasse(...). __init__est le constructeur ;selfdésigne l'objet courant dans ses méthodes.- Les attributs décrivent l'état, les méthodes le comportement ; un attribut de classe est partagé par toutes les instances.
- L'encapsulation (
__attribut) restreint l'accès depuis l'extérieur. - L'héritage (
class Enfant(Parent)) réutilise le code du parent ;super()appelle sa version. - Le polymorphisme permet à des classes différentes de répondre à la même méthode.
- Les méthodes spéciales (
__str__,__eq__,__len__,__call__) intègrent vos objets aux fonctions et opérateurs de Python.
FAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les réponses courtes ci-dessous couvrent les questions les plus recherchées sur la POO en Python. Chaque exemple est autonome et testé sur Python 3.12.
class Voiture:
def __init__(self, marque):
self.marque = marque
ma_voiture = Voiture("Renault")
En Python, tout est objet : les chaînes, listes et dictionnaires en sont déjà.class Personne: # la classe (le moule)
pass
alice = Personne() # un objet (une instance)
bob = Personne() # un autre objet, indépendant
Une seule classe peut produire des milliers d'objets, chacun avec ses propres valeurs d'attributs.self est le premier paramètre des méthodes d'une classe : il désigne l'objet courant, celui sur lequel la méthode est appelée.class Personne:
def __init__(self, nom):
self.nom = nom # attribut de CET objet
def saluer(self):
print(f"Je suis {self.nom}")
alice = Personne("Alice")
alice.saluer() # self vaut alice ici
Python passe self automatiquement : vous écrivez alice.saluer(), pas saluer(alice).__init__ est le constructeur de la classe : Python l'appelle automatiquement quand vous créez un objet, pour initialiser ses attributs.class Personne:
def __init__(self, nom, age):
self.nom = nom
self.age = age
alice = Personne("Alice", 30) # __init__ s'exécute ici
print(alice.nom) # Alice
Les paramètres après self sont les valeurs à fournir lors de l'instanciation.class Vehicule:
def __init__(self, marque):
self.marque = marque
class Voiture(Vehicule):
def __init__(self, marque, modele):
super().__init__(marque) # appelle le parent
self.modele = modele
L'enfant peut ajouter ou redéfinir des méthodes ; super() appelle la version du parent. L'héritage évite de dupliquer le code commun.class CompteBancaire:
def __init__(self, solde):
self.__solde = solde # privé
def afficher_solde(self):
print(self.__solde)
compte = CompteBancaire(1000)
compte.afficher_solde() # OK, via une méthode
# compte.__solde # AttributeError
L'accès passe alors par des méthodes dédiées, ce qui protège la cohérence des données.