web-dev-qa-db-fra.com

Comment fonctionnent les appels de méthode substitués à partir des méthodes de classe de base?

Selon les documents sur l'héritage :

Les classes dérivées peuvent remplacer les méthodes de leurs classes de base. Étant donné que les méthodes n'ont aucun privilège spécial lors de l'appel d'autres méthodes du même objet, une méthode d'une classe de base qui appelle une autre méthode définie dans la même classe de base peut finir par appeler une méthode d'une classe dérivée qui la remplace.

Comment cela se produit-il? Quelqu'un peut-il illustrer ce concept avec un exemple simple?

23
wow yoo

Voici l'exemple que vous avez demandé. Ceci affiche chocolate.

class Base:
    def foo(self):
        print("foo")
    def bar(self):
        self.foo()

class Derived(Base):
    def foo(self):
        print("chocolate")

d = Derived()
d.bar()  # prints "chocolate"

La chaîne chocolate est imprimée à la place de foo car Derived remplace la fonction foo(). Même si bar() est défini dans Base, il finit par appeler l'implémentation Derived de foo() au lieu de l'implémentation Base.

29
IanPudney

Comment ça marche?

Lorsqu'une recherche d'attribut est effectuée sur une instance de la classe, le dictionnaire de classe et les dictionnaires de ses classes de base sont recherchés dans un certain ordre ( voir: Ordre de résolution des méthodes ) pour la méthode appropriée. Ce qui est trouvé d'abord va être appelé.

En utilisant l'exemple Spam suivant:

class Spam:
    def produce_spam(self):
        print("spam")
    def get_spam(self):
        self.produce_spam()

class SuperSpam(Spam):
    def produce_spam(self):
        print("super spam")

Spam définit les fonctions produce_spam et get_spam. Ceux-ci vivent dans son Spam.__dict__ (espace de noms de classe). La sous-classe SuperSpam, par héritage, a accès à ces deux méthodes. SuperSpam.produce_spam ne remplace pas Spam.produce_spam, il est simplement trouvé en premier lors de la recherche du nom 'produce_spam' est créé sur l'une de ses instances.

Essentiellement, le résultat de l'héritage est que les dictionnaires de toutes les classes de base seront également recherchés si, après une recherche d'attribut sur la sous-classe, l'attribut ne se trouve pas dans le dictionnaire de la sous-classe.

Lorsque la fonction get_spam est d'abord invoqué avec:

s = SuperSpam()
s.get_spam()

la séquence des événements à peu près va comme ceci:

  • Examinez SuperSpams __dict__ pour get_spam.
  • Puisqu'il ne se trouve pas dans SuperSpams __dict__ examinez les dictionnaires de ses classes de base (chaîne mro).
  • Spam est le suivant dans la chaîne mro, donc get_spam se trouve dans le dictionnaire de Spam.

Maintenant, quand produce_spam est recherché dans le corps de get_spam avec self.produce_spam, la séquence est beaucoup plus courte:

  • Regardez dans SuperSpam (self) __dict__ pour produce_spam.
  • Trouvez-le, obtenez-le et appelez-le.

produce_spam se trouve dans le __dict__ d'abord pour que cela soit récupéré.

class Base():
    def m1(self):
        return self.m2()
    def m2(self):
        return 'base'

class Sub(Base):
    def m2(self):
        return 'sub'

b = Base()
s = Sub()
print(b.m1(), s.m1())

imprime "base sub"

8
Terry Jan Reedy

Pour illustrer son fonctionnement, considérez ces deux classes:

class Parent(object):
    def eat(self):
        print("I don't want to eat that {}.".format(self.takefrompocket()))

    def takefrompocket(self):
        return 'Apple'

    def __getattribute__(self, name):
        print('Looking for:', name)
        method_to_use = object.__getattribute__(self, name)
        print('Found method:', method_to_use)
        return method_to_use

class Child(Parent):
    def takefrompocket(self):
        return 'salad'

Le __getattribute__ La méthode est responsable dans les nouvelles classes de style (comme toutes les classes en python3) de la recherche d'attribut. Il est simplement implémenté pour print ce que fait chaque recherche - normalement vous ne voulez pas et ne devez pas l'implémenter vous-même . La recherche suit les pythons ordre de résolution des méthodes (MRO) juste si vous êtes vraiment intéressé.

>>> some_kid = Child()
>>> some_kid.eat()
Looking for: eat
Found method: <bound method Parent.eat of <__main__.Child object at 0x0000027BCA4EEA58>>
Looking for: takefrompocket
Found method: <bound method Child.takefrompocket of <__main__.Child object at 0x0000027BCA4EEA58>>
I don't want to eat that salad.

Ainsi, lorsque vous souhaitez utiliser eat, il utilise Parent.eat dans cet exemple. Mais self.takefrompocket est utilisé à partir de Child.

>>> some_parent = Parent()
>>> some_parent.eat()
Looking for: eat
Found method: <bound method Parent.eat of <__main__.Parent object at 0x0000027BCA4EE358>>
Looking for: takefrompocket
Found method: <bound method Parent.takefrompocket of <__main__.Parent object at 0x0000027BCA4EE358>>
I don't want to eat that Apple.

Ici, les deux méthodes sont extraites de Parent. Les classes héritées n'interfèrent pas (généralement) avec leurs ancêtres!

1
MSeifert