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.