Autour du codeDevelopper toujours mieux
Posté le

La redéfinition de méthode

Lorsque l'on hérite d'une classe il arrive souvent que l'on souhaite modifier une méthode, la redéfinition de méthode, c'est le fait de changer une méthode qui existe déjà dans la classe mère.

Pour exemple, voici une classe qui distribue des boissons

class Distributeur:
    def __init__(self, boissons):
        self._boissons = boissons

    def a_boire(self):
        "return None s'il n'y a plus de boisson"
        if self._boissons:
            return self._boissons.pop(0)
        return None

ça s'utilise comme ça

clooney = Distributeur(["café", "café"])
print(clooney.a_boire()) # "café"
print(clooney.a_boire()) # "café"
print(clooney.a_boire()) # None

On voudrait afficher le nombre de café distribuer en ajoutant une méthode nb_boisson_vendu Voici la pire manière de faire.

class DistributeurCompteur(Distributeur):
    def __init__(self, boissons):
        self._boissons = boissons
        self._nb_boisson = 0

    def a_boire(self):
        if self._boissons:
            self._nb_boisson -= 1
            return self._boissons.pop()
        return None

    def nb_boisson_vendu(self):
        return self._nb_boisson

En fait en fessant cela, on a réécrit l'intégralité de la classe. Cela pose deux problèmes le premier et que c'est une perte de temps le deuxième et que si du code marche, il n'y a aucune raison d'y toucher.

La fonction super

Lorsque l'on redéfinie une méthode, on va appeler la méthode de la classe mère dans la méthode de la classe fille en utilisant la fonction super. En python3 super s'utilise sans paramètre en python2 il faut donner la classe parent et self.

Voici la bonne façon de faire. smile

class DistributeurCompteur(Distributeur):
    def __init__(self, boissons):
        super().__init__(boissons)
        self._nb_boisson = 0
    def a_boire(self):
        boisson = super(DistributeurCompteur, self).a_boire(boissons)
        if boisson is not None:
            self._nb_boisson += 1
        return None
    def nb_boisson_vendu(self):
        return self._nb_boisson

class DistributeurCompteur(Distributeur):
    def __init__(self, boissons):
        super(DistributeurCompteur, self).__init__(boissons)
        self._nb_boisson = 0

    def a_boire(self):
        boisson = super(DistributeurCompteur, self).a_boire(boissons)
        if boisson is not None:
            self._nb_boisson += 1
        return None

    def nb_boisson_vendu(self):
        return self._nb_boisson

Le préfix double underscore __

Lorsque vous écrivez une classe il se peut que vous tendiez des pièges aux gens qui souhaiteraient hériter de vos classes. innocent Vous avez écrit la classe trajet pour calculer le temps d'un trajet et sa distance

from math import sqrt

class Trajet:
    def __init__(self, p1, p2, vitesse):
        """
        p1 - Point de départ (x, y)
        p2 - Point d'arrivé  (x, y)
        vitesse - nombre représentant la vitesse de déplacement.
        """
        self.p1 = p1
        self.p2 = p2
        self.vitesse = vitesse

    def distance(self):
        """
        Retourne la distance du trajet.
        """
        x1, y1 = self.p1
        x2, y2 = self.p2
        return sqrt(abs(x1 - x2) ** 2 + abs(y1 - y2) ** 2)

    def temps(self):
        """
        Retourne le temps du trajet.
        """
        return self.distance() * self.vitesse

George a créé un site de co-voiturage et souhaite utiliser votre classe, mais dans son programme le méthode distance doit retourner une chaîne de caractère. Pour adapter votre code il hérite de votre classe et redéfinie la méthodes.

class TrajetMax(Trajet):
    def distance(self):
        return "9O km"


print(TrajetMax((10, 3), (1, 4), 3).distance()) # "9O km"
print(TrajetMax((10, 3), (1, 4), 3).temps()) # "9O km9O km9O km"

Mais voilà, la méthode temps ne marche plus. Le problème, c'est que maintenant, trajet utilise la méthode redéfinie pour calculer le temps. Une solution est de protéger les noms de méthode appelées par d'autre en les préfixant avec un double underscore __

class Trajet:
    def __init__(self, p1, p2, vitesse):
        self.p1 = p1
        self.p2 = p2
        self.vitesse = vitesse

    def __distance(self):
        x1, y1 = self.p1
        x2, y2 = self.p2
        return sqrt(abs(x1 - x2) ** 2 + abs(y1 - y2) ** 2)

    def temps(self):
        return self.__distance() * self.vitesse

class TrajetMax(Trajet):
    def distance(self):
        return "{} km".format(super(TrajetMax, self).distance())


print(TrajetMax((10, 3), (1, 4), 3).temps()) # 27.1661554144

Super ! Mais comment ça marche ?

C'est ce que l'on appelle le substantypage. En python quand un attribut d'une classe est prefixée par __ il se transforme en _Nomdeclass__attr

class Foo:
    def __boo(self):
        print "ok"

Foo().__boo()     # "AttributeError: 'Foo' object has no attribute '__boo'
Foo()._Foo__boo() # ok

Par contre, si l'on fait comme ça, un autre problème se pose, on n'a plus de méthode publique dans Trajet pour avoir la distance. La solution est de garder notre méthode publique est de créer un alias privé.

class Trajet:
    def __init__(self, p1, p2, vitesse):
        self.p1 = p1
        self.p2 = p2
        self.vitesse = vitesse

    def distance(self):
        x1, y1 = self.p1
        x2, y2 = self.p2
        return sqrt(abs(x1 - x2) ** 2 + abs(y1 - y2) ** 2)

    __distance = distance # Creer un alias privé de distance.

    def temps(self):
        return self.__distance() * self.vitesse

Pour conclure, ce troisième chapitre sur les bases de la POO lorsque vous redéfinissez des méthodes, vous devez utiliser super pour appeler les méthodes de la classe mère. Dans votre classe si une méthode est utilisée par d'autre méthode: Elle doit être préfixée par __ si elle est privée. Si elle doit être publique, créez un alias __ma_methode = ma_methode et utilisé l'alias dans les autres méthodes.

Vous avez les bases de la POO en python, donc à vos IDE et bon code! smile