Plan du site  
français  English
pixel
pixel

Articles - Étudiants SUPINFO

Chapitre 04 - Autour de la notion de polymorphisme

Redéfinition de méthodes

Après avoir rappelé la définition du concept de polymorphisme, on va voir comment l'implémenter en Python.

Rappel du concept

Le polymorphisme est le mécanisme qui permet à une classe fille de redéfinir une méthode dont elle a hérité de sa classe mère, tout en gardant la même signature.

[Note]

Rappelons que la signature d'une fonction en Python est son nom et la liste de ses paramètres.

L'intérêt majeur est d’adapter le traitement qu’effectue la méthode aux spécificités de la classe fille. Et ce sous un même nom ce qui permet d'avoir des codes génériques.

Mise en place

En Python il n'y a aucune difficulté à mettre en oeuvre le polymorphisme, il suffit juste d’implémenter la méthode en question dans la classe mère et la classe fille.

Le choix de la "bonne" méthode à utiliser se fera alors automatiquement selon la nature de l’objet, i.e. selon qu'il appartienne à la classe mère ou à la classe fille.

[Note]

Le polymorphisme est également natif en Java et C#.

Mais il ne l'est pas en C++, où l'on doit manuellement l'implémenter.

Example 1.1. Des classes "rectangle" et "pavé droit"

On va déclarer des classes "rectangle" et "pavé droit", la seconde héritant de la première, et comportant chacune une méthode "surface" possédant la même signature :

class rectangle:
    def __init__(self,x,y):
        self._x = x
        self._y = y
    def surface(self):
        return self._x*self._y

class paveDroit(rectangle):
    def __init__(self,x,y,z):
        super().__init__(x,y)
        self.__z = z
    def surface(self):
        return 2*(self._x*self._y+self._x*self.__z+self._y*self.__z)

photo = rectangle(3,4)
print(photo.surface())
weston = paveDroit(3,4,10)
print(weston.surface())
12
164

Selon la nature réelle de l'objet, un rectangle ou un pavé droit, c'est la méthode surface de la "bonne" classe qui sera appelée.


Surcharge des opérateurs

Principe

Lorsque l’on crée des classes, il est naturel de vouloir adapter les opérateurs usuels afin de pouvoir les appliquer sur des objets de nos propres classes. Par opérateurs usuels, on entend en particulier les opérateurs arithmétiques et logiques. Cette création de nouvelles versions s’appelle une surcharge.

Example 1.2. Des surcharges dans la classe "rectangle"

Pour la classe “rectangle“ précédente, on pourra par exemple vouloir surcharger l’opérateur “<“ afin qu’il classe les rectangles selon la valeur de leur surface.

Ou l’opérateur “*” qui multipliera les largeurs et longueurs de deux rectangles afin d’en former un troisième.


Mise en place

On va considérer les opérateurs que l’on souhaite surcharger comme des méthodes de la classe.

Le nom de la méthode correspondante à un opérateur sera précédé et suivi par un double underscore :

__NomOperateur__

Si l’opérateur en question est unaire, la méthode n’aura que l’objet courant en paramètre :

def __NomOperateur__(self):
    ...

Si l’opérateur en question est binaire, la méthode aura l’objet courant en paramètre ainsi qu’un autre objet :

def __NomOperateur__(self,other):
    ...
[Note]

Tout cela est très naturel. Conventionnellement, et parce que cela est bien pratique, on appelle le second paramètre d'un opérateur binaire "other."

Voici pour chacun des opérateurs arithmétiques binaires le nom de l'opérateur et donc la signature de la méthode correspondante :

Opération Nom de l'opérateur associé
+ __add__(self,other)
- __sub__(self,other)
* __mul__(self,other)
/ __div__(self,other)
// __floordiv__(self,other)
% __mod__(self,other)
** __pow__(self,other)
[Note]

Comme toujours lorsque l'on apprend à programmer, on ne se pollue pas l'esprit à essayer de retenir des noms par coeur. On retient juste qu'ils existent et on se réfère au cours si besoin est.

Voici pour chacun des opérateurs binaires d'affectation avec opération arithmétique le nom de l'opérateur et donc la signature de la méthode correspondante :

Opération Nom de l'opérateur associé
+= __iadd__(self,other)
-= __isub__(self,other)
*= __imul__(self,other)
/= __idiv__(self,other)
//= __ifloordiv__(self,other)
%= __imod__(self,other)
**= __ipow__(self,other)

Voici pour certains opérateurs unaires le nom de l'opérateur et donc la signature de la méthode correspondante :

Opération Nom de l'opérateur associé
- __neg__(self,other)
abs() __abs__(self,other)
bin __bin__(self,other)
oct __oct__(self,other)
hex __hex__(self,other)

Voici pour chacun des opérateurs logiques binaires le nom de l'opérateur et donc la signature de la méthode correspondante :

Opération Nom de l'opérateur associé
< __lt__(self,other)
<= __le__(self,other)
> __gt__(self,other)
>= __ge__(self,other)
== __eq__(self,other)
!= __ne__(self,other)

Exemples

On va présenter dans ce paragraphe quelques exemples de surcharges d'opérateurs.

Example 1.3. Surcharge des opérateurs * et *= dans la classe "rectangle"

On définit ici le résultat de la multiplication de deux rectangles comme étant le rectangle dont la largeur (resp. longueur) est le produit des largeurs (resp. longueurs) :

class rectangle:
    def __init__(self,x,y):
        self.__x = x
        self.__y = y
    def surface(self):
        return self.__x*self.__y
    def __mul__(self, other):
        return rectangle(self.__x*other.__x,self.__y*other.__y)
    def __imul__(self, other):
        self = self*other
        return self

a = rectangle(3,4)
b = rectangle(2,5)
c = a*b
print(c.surface())
c *= b
print(c.surface())
120
1200

[Note]

L’implémentation de __imul__ de cette façon n’est possible que parce que l’on a implémenté __mul__ avant. On aurait aussi bien sûr pu effectuer des calculs directement sur les attributs.

Ne pas oublier de retourner self à la fin de la méthode __imul__, afin de pouvoir réaliser l'affectation.

Example 1.4. Surcharge des opérateurs < et == dans la classe "rectangle"

On choisit d'effectuer une comparaison de deux rectangles selon la valeur de leur surface :

class rectangle:
    def __init__(self,x,y):
        self.__x = x
        self.__y = y
    def surface(self):
        return self.__x*self.__y
    def __lt__(self, other):
        return self.surface() <  other.surface()
    def __eq__(self, other):
        return self.__x == other.__x and self.__y == other.__y

a = rectangle(3,4)
b = rectangle(2,7)
print(a < b)
print(a == b)
True
False

Le cas particulier de l'affichage

Le but ici est de pouvoir afficher des informations relatives à un objet en utilisant la commande “print”.

Pour cela on doit surcharger dans notre classe un opérateur unaire nommé __str__ :

def __str__(self):
    ...

À noter que cette méthode devra nécessairement retourner une chaîne de caractères, qui sera celle affichée sur la console.

Un exemple permettra de bien comprendre le mécanisme.

Example 1.5. Surcharge de l'opérateur __str__ dans la classe "rectangle"

class rectangle:
    def __init__(self,x,y):
        self.__x = x
        self.__y = y
    def surface(self):
        return self.__x*self.__y
    def __str__(self):
        return "longueur : "+str(self.__x)+", largeur : "+str(self.__y)

a = rectangle(3,4)
print(a)
longueur : 3, largeur : 4

Le Duck Typing

Principe

En Python on ne précise pas les types attendus des paramètres des fonctions, et donc des méthodes.

Cela implique que l’on peut utiliser une fonction (resp. méthode) avec des paramètres de n’importe quel type, à la condition que les opérations de la fonction (resp. méthode) soient compatibles avec les types des paramètres.

La morale de cette histoire c'est qu'en Python on s’intéresse donc plus au comportement d’un objet qu’à son type réel. Si les méthodes d’un objets sont compatibles avec les opérations requises par une fonction, alors la fonction en question peut s’appliquer sur cet objet.

Voici une petite citation pittoresque qui illustre ce concept et justifie son nom de "Duck Typing" :

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

Dans sa version française :

Si un animal ressemble à un canard, nage comme un canard et cancane comme un canard, alors il s’agit probablement d’un canard.

Et pour les cinéphiles amateurs de dialogues truculents :

Y a pas à dire, dans la vie il faut toujours se fier aux apparences. Quand un homme a un bec de canard, des ailes de canard et des pattes de canard, c'est un canard. Et ce qui est vrai pour les canards est vrai aussi pour les petits merdeux.

--Michel Audiard

Exemple

On va présenter dans ce paragraphe un exemple concret de Duck Typing.

Example 1.6. Une fonction compatible avec des objets de deux classes différentes

On va déclarer des classes "rectangle" et "disque" comportant chacune une méthode "surface", puis une fonction "peinture" utilisant une telle méthode :

class rectangle:
    def __init__(self,x,y):
        self.__x = x
        self.__y = y
    def surface(self):
        return self.__x*self.__y

class disque:
    def __init__(self,r):
        self.__r = r
    def surface(self):
        return 3.14*self.__r**2

def peinture(objet):
    return 2*objet.surface()

cd = disque(10)
photo = rectangle(20,15)
print(peinture(cd))
print(peinture(photo))
628.0
600

L'explication est simple : les classes "rectangle" et "disque" ont toutes deux une méthode "surface" et la fonction "peinture" n’utilise que la méthode "surface" d’un objet. Le Duck Typing peut donc s’appliquer, et la méthode "surface" peut ainsi prendre en paramètre des objets "rectangle" et "disque".


A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels