4. Programmation orientée objet

4.1. Les objets

Nous avons utilisé ce terme auparavant pour désigner de façon générique ce que contient une variable : un nombre, une liste, une chaîne de caractère, une fonction, etc. Il est possible en Python de créer des objets dont le type est personnalisé. Ces objet auront pour objectif de stocker des données et de pouvoir les traiter à l’aide de méthode que l’on aura définie.

Nous allons illustrer dans ce chapitre l’utilisation d’objets pour réaliser un annuaire personnalisé.

4.2. Les attributs

Voici un exemple :

class Personne():
    nom = ""

Que l’on peut utiliser de la façon suivante :

>>> jean = Personne()
>>> jean.nom = "Dupont" # modification de l'attribut
>>> jean.prenom = "Jean" # On peut rajouter un attribut
>>> print(jean.nom)
Dupont

Nous avons tout d’abord créé un nouveau type d’objet. Il s’agit du type Personne. On appelle cela une class. Cette class contient pour l’instant un attribut : le nom. Pour créer un objet, on appelle le nom de la class. On peut lire ou écrire un attribut simplement. Cet objet est donc modifiable (mutable). On peut aussi rajouter un attribut (ici, le prénom) à l’objet.

Pour cet exemple, nous aurions pu utiliser une liste (ou un tuple) pour stocker le nom et le prénom dans une seule variable, mais l’objet rend le code beaucoup plus lisible (jean.nom est plus explicite que jean[0]). C’est un premier avantage.

4.3. Les méthodes

Nous avons vu que les attributs permettent de stocker les informations.

On peut alors imaginer la création de fonctions utilisant ces objets :

def bonjour(personne):
    print("Bonjour", personne.prenom, personne.nom, '!')

Plutôt que définir une fonction indépendamment de l’objet, il est possible d’attacher cette fonction à l’objet. C’est ce qu’on appelle une méthode

class Personne():
    def bonjour(self):
        print("Bonjour", self.prenom, self.nom, '!')

jean = Personne()
jean.nom = "Dupont"
jean.prenom = "Jean"
jean.bonjour()

Toute fonction définie dans le corps de la class est une méthode. Le premier argument de la méthode est l’objet lui même que l’on nomme conventionnellement self. L’appel d’une méthode se fait de la façon suivante : objet.nom_methode(tous_les_arguments_sauf_self).

On voit ici un deuxième avantage de l’utilisation d’objet : la variable contient des fonctions (les méthodes). L’utilisateur de la variable ne doit pas ce soucier de la façon de réaliser une fonction ni de l’importer. Il fait confiance à la variable qui sait comment faire. Ceci permet en particulier d’avoir pour deux variables différentes, deux méthodes différentes ayant le même nom.

4.4. Méthodes spéciales

Il existe plusieurs méthodes qui ont des rôles bien précis

4.4.1. Méthode __init__

Cette méthode sera appelée automatiquement lors de la création de l’objet. On peut l’utiliser pour initialiser des attributs :

class Personne():
    def __init__(self, nom="", prenom=""):
        self.nom = nom
        self.prenom = prenom
    def bonjour(self):
        print("Bonjour", self.prenom, self.nom, '!')

Ce qui donne :

>>> jean = Personne("Dupont", "Jean")
>>> jean.bonjour()
Bonjour Jean Dupont !

4.4.2. Méthode __repr__

Cette méthode sert a représenter l’objet sous forme d’une chaîne de caractère. Elle est utilisé par la fonction print :

class Personne():
    def __init__(self, nom="", prenom=""):
        self.nom = nom
        self.prenom = prenom
    def bonjour(self):
        print("Bonjour", self.prenom, self.nom, '!')
    def __repr__(self):
        return self.prenom + " " + self.nom

Ce qui donne:

>>> jean = Personne(prenom="Jean", nom="Dupont")
>>> print(jean)
Jean Dupont

4.5. Exemple

On veut pouvoir créer des liens d’amitié (à la facebook) entre les personnes. Ainsi chaque personnes a un ensemble d’amis. Cette ensemble, initialement vide, est créé au moment de l’initialisation de l’objet. Il faut alors pouvoir rajouter un ami ou afficher la liste des amis :

class Personne():
    def __init__(self, nom="", prenom=""):
        self.nom = nom
        self.prenom = prenom
        self.liste_amis = set()
    def bonjour(self):
        print("Bonjour", self.prenom, self.nom, '!')
    def ajoute_ami(self, ami):
        self.liste_amis.add(ami)
    def affiche_amis(self):
        print("Les amis de", self.__repr__(), "sont :")
        for ami in self.liste_amis:
            print(ami)
    def __repr__(self):
        return self.prenom + " " + self.nom

Que l’on utilise de la façon suivante :

>>> jean = Personne(prenom="Jean", nom="Dupont")
>>> jacques = Personne(prenom="Jacques", nom="Dupond")
>>> pierre = Personne(prenom="Pierre", nom="Martin")
>>> jean.ajoute_ami(jacques)
>>> jean.ajoute_ami(pierre)
>>> jean.affiche_amis()  
Les amis de Jean Dupont sont :
Jacques Dupond
Pierre Martin

Exercice

Les amis de mes amis sont mes amis ! Écrire (en trois lignes) une méthode qui ajoute automatiquement les amis de mes amis à mes amis.

4.6. Héritage

C’est le dernier point important concernant les objets en Python. Nous avons vu comment à l’aide du mot clé class définir une classe d’objet. Dans cette classe, nous avons défini l’ensemble des attributs et des méthodes. Il est cependant possible d’hériter des méthodes d’une autre classe - ce qui permet d’avoir plusieurs classes possédant des méthodes identiques.

Continuons notre exemple : suivant la nationalité, je souhaite pouvoir écrire un message de bonjour différent. Pour cela, je vais créer une classe PersonneFrancaise et PersonneAnglaise :

class Personne():
    def __init__(self, nom="", prenom=""):
        self.nom = nom
        self.prenom = prenom
        self.liste_ami = set()
    def ajoute_ami(self, ami):
        self.liste_ami.add(ami)
    def affiche_amis(self):
        print("Les amis de", self.__repr__(), "sont :")
        for ami in self.liste_amis:
            print(ami)
    def __repr__(self):
        return self.prenom + " " + self.nom

class PersonneAnglaise(Personne):
    def bonjour(self):
        print("Hello", self.prenom, self.nom, '!')

class PersonneFrancaise(Personne):
    def bonjour(self):
        print("Bonjour", self.prenom, self.nom, '!')

Ce qui donne :

>>> john = PersonneAnglaise(prenom="John", nom="Dupont")
>>> john.bonjour()
Hello John Dupont !

Du côté de l’utilisateur, celui qui reçoit la variable john ne veut pas savoir si john est français ou anglais, il ne veut même pas savoir comment dire bonjour à un anglais. Par contre, il sait que pour dire bonjour à john, il suffit de faire john.bonjour().

Du côté du programmeur, on applique ici le principe DRY (Don’t Repeat Yourself), ou le principe de ne pas faire du copier-coller dans son code. Les méthodes qui sont communes ne sont pas dupliquées.

4.7. Emulation d’un type de donnée

Exemple : une liste de course

4.8. Quand faut-il utiliser des objets ?

L’idée générale de l’utilisation d’objet est de supprimer la difficulté pour l’utilisateur et de la déplacer au niveau de la définition des méthodes de la classe. Dans cette methode, on aura accès à la plupart des paramètres nécéssaires pour executer une action.

La difficulté n’est généralement pas de savoir quand il faut utiliser une objet (le réponse est tout le temps!), mais de réussir à identifier dans un problème quels sont les objets à définir.

Les objets permettent souvent de décrire des objets réél. Voici quelques exemples :

  • Un livre: C’est un objet qui possède comme attribut : un titre, auteur, ... . Mais aussi une liste de chapitres, lequels ont un titre et des parties, ... On peut alors imaginer une méthode qui renvoie un sommaire du livre (méthode avec comme argument la profondeur que l’on souhaite du sommaire). Une méthode qui renvoie le nombre de chapitre, ...
  • Un circuit électronique : il y a des composants électronique et des connections entre le composants. Pour les composants, on aura plusieurs classes qui définiront chacun un type de composant (diôle linéaire passif, transistor, amplificateur opérationnel, ...). Chaque composant aura des pattes (un autre type d’objet). Une connexion rassemblera les pattes des composants. L’objectif de ces objets est de décrire complètement le circuit électronique. On pourra alors imaginer des méthodes plus ou moins complexes : faire une liste triée des composants que l’on devra acheter ou calculer la réponse impulsionnel du circuit...
  • Un instrument de mesure : par exemple un oscilloscope, il aura des méthodes pour changer l’échelle verticale ou horizontale, récupérer la courbe, etc.
  • On souhaite modéliser le sysème solaire : on pourra créer une classe Planete et une classe Systeme. Un planete contient un nom et une masse. Le système enregistrera pour chaque planete qu’on lui ajoute sa position et sa vitesse. Le système sera capable d’être représenté graphiquement. Il pourra créer l’ensemble d’équation différentiel qui sera ensuite utilisé par un solveur afin de connaitre la position des planète à un autre instant. Dans cet exemple, l’utilisation d’objet est d’aucune utilité pour la résolution en elle même du problème (qui est un exercice classique). Par contre, elle permet de fournir une interface avec l’utilisateur qui va facilité l’entrée des paramètres ou leur visualisation.
  • Les objets sont utilisés pour décrire des structures complexes dans un ordinateur : par exemple la fenètre de l’interface graphique d’une application est un objet (qui contient d’autres object comme les boutons, les menus, ...). En python, Matplotlib représente une figure par un objet.
  • En physique, on pourra utilier un objet pour représenter l’ensemble des paramètres d’un problème ou les résultat d’une mesure. Par exemple, l’image prise par un telescope sera un objet qui contiendra l’image, mais aussi le moment de la prise de vue, sa durée, l’orientation du téléscope. Cet objet devra pouvoir être enregistré sur un fichier et créé à partir de ce fichier. Une autre classe permettra de faire une analyse de cette image. Elle s’initialisera avec une image et aura des méthodes pour faire l’analyse (appliquer des filtres, mesurer des distances entre étoiles, ...).