Jaki jest sens dziedziczenia w Pythonie?

Załóżmy, że masz następującą sytuację

<code>#include <iostream>

class Animal {
public:
    virtual void speak() = 0;
};

class Dog : public Animal {
    void speak() { std::cout << "woff!" <<std::endl; }
};

class Cat : public Animal {
    void speak() { std::cout << "meow!" <<std::endl; }
};

void makeSpeak(Animal &a) {
    a.speak();
}

int main() {
    Dog d;
    Cat c;
    makeSpeak(d);
    makeSpeak(c);
}
</code>

Jak widać, makeSpeak jest procedurą, która akceptuje ogólny obiekt zwierzęcy. W tym przypadku Animal jest bardzo podobny do interfejsu Java, ponieważ zawiera tylko czystą metodę wirtualną. makeSpeak nie wie, jaka jest natura zwierzęcia. Po prostu wysyła sygnał „mówić” i pozostawia późne wiązanie, aby zadecydować, którą metodę wywołać: albo Cat :: speak () albo Dog :: speak (). Oznacza to, że jeśli chodzi o makeSpeak, wiedza o tym, która podklasa jest faktycznie przekazana, jest nieistotna.

Ale co z Pythonem? Zobaczmy kod tego samego przypadku w Pythonie. Pamiętaj, że przez chwilę staram się być jak najbardziej podobny do sprawy C ++:

<code>class Animal(object):
    def speak(self):
        raise NotImplementedError()

class Dog(Animal):
    def speak(self):
        print "woff!"

class Cat(Animal):
    def speak(self):
        print "meow"

def makeSpeak(a):
    a.speak()

d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
</code>

Teraz w tym przykładzie widzisz tę samą strategię. Używasz dziedziczenia, aby wykorzystać hierarchiczną koncepcję zarówno psów, jak i kotów będących zwierzętami. Ale w Pythonie nie ma takiej hierarchii. To działa równie dobrze

<code>class Dog:
    def speak(self):
        print "woff!"

class Cat:
    def speak(self):
        print "meow"

def makeSpeak(a):
    a.speak()

d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
</code>

W Pythonie możesz wysłać sygnał „mówić” do dowolnego obiektu, który chcesz. Jeśli obiekt jest w stanie sobie z tym poradzić, zostanie wykonany, w przeciwnym razie zostanie zgłoszony wyjątek. Załóżmy, że dodajesz samolot klasy do obu kodów i przesyłasz obiekt samolotu do makeSpeak. W przypadku C ++ nie skompiluje się, ponieważ samolot nie jest klasą pochodną Animal. W przypadku Pythona wystąpi wyjątek w czasie wykonywania, który może być nawet oczekiwanym zachowaniem.

Z drugiej strony załóżmy, że dodajesz klasę MouthOfTruth za pomocą metody speak (). W przypadku C ++ albo będziesz musiał zmienić swoją hierarchię, albo będziesz musiał zdefiniować inną metodę makeSpeak, aby zaakceptować obiekty MouthOfTruth, lub w java możesz wyodrębnić zachowanie do CanSpeakIface i zaimplementować interfejs dla każdego. Istnieje wiele rozwiązań ...

Chciałbym podkreślić, że nie znalazłem jeszcze jednego powodu, aby używać dziedziczenia w Pythonie (poza ramami i drzewami wyjątków, ale myślę, że istnieją alternatywne strategie). nie musisz implementować hierarchii bazowej, aby działać polimorficznie. Jeśli chcesz użyć dziedziczenia do ponownego wykorzystania implementacji, możesz to samo osiągnąć poprzez przechowywanie i delegowanie, z dodatkową korzyścią, którą możesz zmienić w czasie wykonywania, i jasno zdefiniujesz interfejs zawartego w nim pakietu, nie ryzykując niezamierzonych skutków ubocznych.

W końcu pytanie brzmi: jaki jest sens dziedziczenia w Pythonie?

Edytować: dzięki za bardzo interesujące odpowiedzi. Rzeczywiście możesz go użyć do ponownego wykorzystania kodu, ale zawsze uważam, kiedy ponownie używam implementacji. Ogólnie rzecz biorąc, robię bardzo płytkie drzewa dziedziczenia lub w ogóle drzewa, a jeśli funkcjonalność jest powszechna, refaktoryzuję ją jako zwykłą procedurę modułową, a następnie wywołuję ją z każdego obiektu. Widzę zaletę posiadania jednego punktu zmiany (np. Zamiast dodawać do Dog, Cat, Moose i tak dalej, po prostu dodaję do Animal, która jest podstawową zaletą dziedziczenia), ale można to osiągnąć za pomocą łańcuch delegacji (np. a la JavaScript). Nie twierdzę, że jest lepiej, tylko inny sposób.

Ja też znalazłempodobny post w tym względzie.

questionAnswers(11)

yourAnswerToTheQuestion