Bruno Giarrizzo

Python

< Retour au module

Classes

Définition d’une classe

Pour définir une classe basique, en python, c'est très simple :

class Personne:
    nom = "Hyweene"

personneA = Personne()
personneB = Personne()

Dans cet exemple, je déclare une classe avec un attribut de classe qui a une valeur par défaut, ici Hyweene.

Je l'instancie deux fois :

print(personneA) # <__main__.Personne object at 0x732dd6b33650>
print(personneB) # <__main__.Personne object at 0x732dd6b33aa0>

print(personneA.nom) # Affiche "Hyweene"
print(personneB.nom) # Affiche "Hyweene"

Une fois la classe instanciée, on peut créer ou modifier un attribut sur une instance précise. Cela n’affecte pas l’attribut de classe lui-même, mais crée un attribut d’instance qui masque la valeur par défaut. Chaque instance peut donc avoir sa propre valeur :

personneA.nom = "Michel"
personneB.nom = "JeanLuc"

Sans pour autant modifier la valeur dans la seconde instance :

print(personneA.nom) # Affiche "Michel"
print(personneB.nom) # Affiche "JeanLuc"

Pour pouvoir définir la valeur de l'attribut à l'instanciation (à la création de l'objet), il vaudra mieux passer par un constructeur ( __init__).

Constructeur (init) et attributs d’instance

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

personne = Personne(nom="Hyweene", age=36)

Ici, j'ai défini un constructeur qui me permettra d'attribuer une valeur voulue tout de suite à l'instanciation.

Voici le résultat :

print(personne) # Affiche <__main__.Personne object at 0x732dd6f8f380>
print(personne.nom) # Affiche "Hyweene"
print(personne.age) # Affiche 36

Si par erreur j'oubliais de définir une valeur pour l'attribut nomou age, l'interpreteur python me renverrait une erreur :

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

personne = Personne()

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Personne.__init__() missing 1 required positional argument: 'nom'

Méthodes d’instance, de classe, statiques

Pour définir les différentes méthodes, je vais réutiliser ma classe Personne:

Méthodes d'instance

Une méthode d'instance est utilisée le plus fréquemment pour manipuler des données de l'objet instancié.

L'argument selfreprésente l'instance qui appelle la méthode, on y fait référence pour utiliser les différents attributs.

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

    def presentation(self):
        print(f"Bonjour, Je m'appelle {self.nom}, j'ai {self.age} ans.")

Ici, je définis mon objet Personne, son nom et age, puis une fonction qui permettra de présenter cette personne :

personne = Personne(nom="Hyweene", age=36)
personne.presentation() # Affiche "Je m'appelle Hyweene, j'ai 36 ans."

Méthodes de classe

Une méthode de classe est utilisée pour des méthodes qui doivent accéder ou modifier des données partagées entre toutes les instances d'une même classe.

Par exemple :

class Personne:
    nombre_de_personnes = 0

    def __init__(self, nom, age):
        self.nom = nom
        self.age = age
        Personne.nombre_de_personnes += 1

    @classmethod
    def compteur_personnes(cls):
        return cls.nombre_de_personnes

Je définis un compteur de population (on imaginera que le nombre d'instance = population) :

personne1 = Personne(nom="machin", age=25)
personne2 = Personne(nom="truc", age=32)
personne3 = Personne(nom="bidule", age=10)
personne4 = Personne(nom="chose", age=54)
personne5 = Personne(nom="bob", age=38)
personne6 = Personne(nom="alice", age=63)

On a bien créé 6 instances de personne, et si on appelle la méthode compteur_personnes(), dans n'importe quelle instance, on aura toujours le même nombre :

Personne.compteur_personnes() # Affiche 6

Si, par contre, on appelle compteur_personnes()juste après chaque instanciation de la classe, on verra nombre_de_personness'incrémenter à chaques étapes :

personne1 = Personne(nom="machin", age=25)
print(Personne.compteur_personnes()) # Affiche 1
personne2 = Personne(nom="truc", age=32)
print(Personne.compteur_personnes()) # Affiche 2
personne3 = Personne(nom="bidule", age=10)
print(Personne.compteur_personnes()) # Affiche 3
personne4 = Personne(nom="chose", age=54)
print(Personne.compteur_personnes()) # Affiche 4
personne5 = Personne(nom="bob", age=38)
print(Personne.compteur_personnes()) # Affiche 5
personne6 = Personne(nom="alice", age=63)
print(Personne.compteur_personnes()) # Affiche 6

Méthodes Statiques

Une méthode statique ne reçoit ni self, ni cls. Elle appartient au namespace de la classe, mais ne travaille pas sur la classe ou sur une instance.

Elle est utile pour écrire des utilitaires.

Exemple :

class Math:
    @staticmethod
    def addition(a, b):
        return a + b

print(Math.addition(1,2)) # Affiche 3

Encapsulation : attributs privés et protégés

L'encapsulation est un concept en POO qui vise à cacher les détails internes d'une classe pour proteger ses données et contrôler l'accès.

Ca permet d'éviter des erreurs et faciliter la maintenance du code.

Pourquoi encapsuler ?

Admettons qu'une classe est une boite. Les attributs sont dedans et seules quelques méthodes permettent d'y acceder. Via l'encapsulation, on peut :

Niveaux de visibilité

Exemple :

class Personne:
    def __init__(self, nom):
        self.nom = nom  # Attribut public

personne = Personne("Hyweene")
print(personne.nom)  # Accès direct à l'attribut public
class Personne:
    def __init__(self, nom):
        self._nom = nom  # Attribut protégé

personne = Personne("Hyweene")
print(personne._nom)  # Accès direct à l'attribut protégé (possible mais déconseillé)
class Personne:
    def __init__(self, nom):
        self.__nom = nom  # Attribut privé  

personne = Personne("Hyweene")
print(personne.__nom) 

...

AttributeError: 'Personne' object has no attribute '__nom'

On obtient une erreur car l'attribut est privé.

On peut corriger en ajoutant une méthode publique pour accéder à l'attribut privé :

class Personne:
    def __init__(self, nom):
        self.__nom = nom  # Attribut privé

    def get_nom(self):
        return self.__nom  # Méthode publique pour accéder à l'attribut privé  

personne = Personne("Hyweene")
print(personne.get_nom())  # Affiche "Hyweene"

Héritage simple et surcharge de méthodes

L'héritage est un concept fondamental en POO qui permet de créer une nouvelle classe (classe dérivée ou sous-classe) basée sur une classe existante.

La sous-classe hérite des attributs et des méthodes de la super-classe. On peut facilement réutiliser ce code, et créer des hiérarchies de classes.

Exemple d'héritage simple

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

class Chien(Animal):
    def parler(self):
        return "Le chien aboie."

class Chat(Animal):
    def parler(self):
        return "Le chat miaule."

Ici, j'ai une classe mère : Animalavec une méthode parler().

Quand je créé les classes filles Chienet Chat, elles héritent de la méthode parler(). Je peux les surcharger pour qu'elles aient un comportement spécifique.

animal = Animal()
chien = Chien()
chat = Chat()

print(animal.parler())  # Affiche "L'animal fait un bruit."
print(chien.parler())   # Affiche "Le chien aboie."
print(chat.parler())    # Affiche "Le chat miaule."

Introduction à la composition

La composition permet de construire des objets complexes en combinant des objets plus simples.

class Moteur:
    def demarrer(self):
        return "Moteur démarré."

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

    def demarrer_voiture(self):
        return self.moteur.demarrer()

Ici, j'ai une classe Moteuravec une méthode demarrer().

La classe Voiturecontient une instance de Moteurcomme attribut.

voiture = Voiture()
print(voiture.demarrer_voiture())  # Affiche "Moteur démarré."

Représentation d’objets (str, repr)

Pour personnaliser la représentation en chaîne de caractères d'un objet, on peut définir les méthodes spéciales __str__et __repr__dans une classe.

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

    def __str__(self):
        return f"{self.nom}, {self.age} ans"

    def __repr__(self):
        return f"Personne(nom='{self.nom}', age={self.age})"

Ici, j'ai défini une classe Personneavec un constructeur, puis j'ai ajouté les méthodes __str__et __repr__.

personne = Personne("Hyweene", 36)
print(str(personne))  # Affiche "Hyweene, 36 ans"
print(repr(personne)) # Affiche "Personne(nom='Hyweene', age=36)"