Classes abstraites

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Il arrive en Python orienté objet de créer des classes uniquement pour factoriser le code. Par exemple, une rampe est un objet dont on peut modifier la longueur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class AbstractRamp:
    """An interface shared by all ramps."""

    def lengthen(self):
        self.move(**self.len_args)

    def set_len(self, l):
        dist = l - len(self)

        if dist < 0:
            move = self.shorten
        elif dist > 0:
            move = self.lengthen
        else:
            return

        while 0 <= len(self) <= self.max_len and abs(len(self) - l) > self.accuracy:
            move()

    def shorten(self):
        self.move(**self.short_args)

Une telle classe n'est jamais instanciée directement, seulement héritée. Par exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class BasicRamp(AbstractRamp):
    """A basic ramp with an angle sensor and two solenoid valves.""" 

    def __init__(self, max_len, angle_input, len_args, short_args, accuracy):

        self.max_len = max_len
        self.len_args = len_args
        self.short_args = short_args
        self.angle_input = angle_input
        self.accuracy = accuracy

        AbstractRamp.__init__(self)

    def __len__(self):
        angle = self.get_angle() # Méthode à définir dans une classe fille

        return angle/360.0 * self.max_len

    def move(self, output, delay):
        self.pulse(output) # Méthode à définir dans une classe fille
        self.delay(delay) # Méthode à définir dans une classe fille

La question est : faut-il définir dans la classe abstraite les méthodes et arguments à spécifier dans les classes filles (ici, move, __len__, accuracy, etc.), quitte à mettre des pass dans les méthodes et des valeurs bidons pour les arguments ?

Merci.

+0 -0

Si toutes les classes filles doivent implémenter cette méthode, oui. Pour cela deux solutions (à ma connaissance) :

  • Lever l'exception NotImplementedError. Attention, cette exception sera levée à l'exécution et donc une classe fille peut très bien être instanciée même si elle ne définie pas cette méthode.
  • Utiliser les ABC. C'est une bidouille à base de __metaclass__ qui empèche une classe d'être instanciée si elle n'implémente pas la méthode en question. Voici la doc et un petit exemple en prime (parce que bon la doc …) :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import abc

class MyABC(object):
    __metaclass__ = abc.ABCMeta  # Do not forget this !

    @abc.abstractmethod
    def some_method(self):
        pass

class SomeClass(MyABC):
    def some_method(self):  # some_method is defined
        pass

class OtherClass(MyABC):
    pass  # some_method is *not* defined

ok = SomeClass()
boum = OtherClass()

"""
A l'execution :
Traceback (most recent call last):
  File "testabc.py", line 18, in <module>
    boum = OtherClass()
TypeError: Can't instantiate abstract class OtherClass with abstract methods some_method
"""

Merci.

J'imagine qu'il en va de même pour les attributs, qu'il me faut le initialiser avec une valeur bidon (None ?) ?

En parlant d'attributs, me faut-il les passer explicitement en arguments à la classe (abstraite) mère, ou puis-je profiter de leur propagation de la fille vers la mère ? Par exemple, quel code me faut-il choisir ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Abstract:

    def __init__(self):
        self.arg = None

    def method(self):
        self.arg += 1

class Cls(Abstract):

    def __init__(self, arg):
        Abstract.__init__(self)

        self.arg = arg
        self.pouet = 'Patapouf'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Abstract:

    def __init__(self, arg):
        self.arg = arg

    def method(self):
        self.arg += 1

class Cls(Abstract):

    def __init__(self, arg):
        Abstract.__init__(self, arg)

        self.pouet = 'Patapouf'

Merci. :)

+0 -0

Bonne question.

J'aurais tendance à utiliser la seconde méthode qui oblige les classes filles à passer les arguments. C'est plus explicite que de faire sa cuisine dans la classe fille (et ça peut t'éviter des problèmes d'implémentations, par exemple si une classe fille ne fais pas comme les autres et utilise un autre nom pour un attribut).

Au passage, une feature pythonique plutôt stylée qui est utile pour passer beaucoup d'arguments de manière asser souple : les *args et **kwargs. Si tu ne connais pas, je te préviens, c'est un peu tordu à comprendre (mais google a plein de ressources).


En y réflechissant : définitivement la seconde méthode.

Dans la première, self.arg est mis à None dans la classe mère. En puis en fait on lui assigne une valeur dans la classe fille. Mais peut être pas si cette classe fille ne fais rien. Bref on ne sais pas trop ce qui se passe …

Dans la seconde méthode, tu passes forcément un argument (quoique si il était optionnel ce serait pareil : tu met juste une valeur par défaut à None), donc tu sais d'où viens la valeur. C'est beaucoup plus explicite.

Au passage, une feature pythonique plutôt stylée qui est utile pour passer beaucoup d'arguments de manière asser souple : les args et *kwargs. Si tu ne connais pas, je te préviens, c'est un peu tordu à comprendre (mais google a plein de ressources).

Du coup, je suis obligé d'initialiser ma classe fille de la manière suivante (en mettant les paramètres de la classe mère à la fin), non ?

1
2
3
params_mere = (...)
params_nommes_mere = {...}
obj = Fille(param_fille1, param_fille2, param_nomme_fille=3, *params_mere, **params_nommes_mere)
+0 -0

Au passage, une feature pythonique plutôt stylée qui est utile pour passer beaucoup d'arguments de manière asser souple : les args et *kwargs. Si tu ne connais pas, je te préviens, c'est un peu tordu à comprendre (mais google a plein de ressources).

Du coup, je suis obligé d'initialiser ma classe fille de la manière suivante (en mettant les paramètres de la classe mère à la fin), non ?

1
2
3
params_mere = (...)
params_nommes_mere = {...}
obj = Fille(param_fille1, param_fille2, param_nomme_fille=3, *params_mere, **params_nommes_mere)

Vayel

Tout à fait, c'est exactement ça

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Abstract:
    def __init__(self, arg):
        self.arg = arg
    def method(self):
        self.arg += 1

class Cls(Abstract):
    def __init__(self, arg):
        super().__init__(arg)   
        self.pouet = 'Patapouf'

Tu peux utiliser super() (documentation) pour appeler directement la classe mère.

Un talk de la Pycon 2015 en parle plus longuement et aborde l'ordre de résolution des méthodes dans les cas d'héritance multiples.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte