Какой смысл наследования в Python? - PullRequest
78 голосов
/ 20 июня 2009

Предположим, у вас следующая ситуация

#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);
}

Как видите, makeSpeak - это процедура, которая принимает универсальный объект Animal. В этом случае Animal довольно похож на интерфейс Java, поскольку содержит только чисто виртуальный метод. makeSpeak не знает природу Животного, которого ему передают. Он просто посылает ему сигнал «говорить» и оставляет позднюю привязку, чтобы позаботиться о том, какой метод вызывать: либо Cat :: speak (), либо Dog :: speak (). Это означает, что для makeSpeak знание того, какой подкласс фактически передан, не имеет значения.

А как насчет Python? Давайте посмотрим код для того же случая в Python. Обратите внимание, что я стараюсь быть как можно более похожим на случай C ++:

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)

Теперь в этом примере вы видите ту же стратегию. Вы используете наследование, чтобы использовать иерархическую концепцию, согласно которой собаки и кошки являются животными. Но в Python нет необходимости в этой иерархии. Это работает одинаково хорошо

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)

В Python вы можете послать сигнал «говорить» любому объекту, который вы хотите. Если объект может с ним справиться, он будет выполнен, в противном случае он вызовет исключение. Предположим, вы добавили класс Airplane в оба кода и отправили объект Airplane в makeSpeak. В случае C ++ он не будет компилироваться, поскольку Airplane не является производным классом Animal. В случае с Python это вызовет исключение во время выполнения, что может быть даже ожидаемым поведением.

С другой стороны, предположим, что вы добавили класс MouthOfTruth с методом speak (). В случае C ++ вам придется либо реорганизовать свою иерархию, либо вам придется определить другой метод makeSpeak для приема объектов MouthOfTruth, либо в Java вы можете извлечь поведение в CanSpeakIface и реализовать интерфейс для каждого из них. Есть много решений ...

Что я хотел бы отметить, так это то, что я не нашел ни одной причины для использования наследования в Python (кроме каркасов и деревьев исключений, но я предполагаю, что существуют альтернативные стратегии). вам не нужно реализовывать базовую производную иерархию для полиморфного выполнения. Если вы хотите использовать наследование для повторного использования реализации, вы можете сделать то же самое путем сдерживания и делегирования, с дополнительным преимуществом, которое вы можете изменять во время выполнения, и вы четко определяете интерфейс содержимого, не рискуя непреднамеренными побочными эффектами.

Итак, в конце концов, возникает вопрос: какой смысл наследования в Python?

Редактировать : спасибо за очень интересные ответы. Действительно, вы можете использовать его для повторного использования кода, но я всегда осторожен при повторном использовании реализации. В общем, я склонен создавать очень мелкие деревья наследования или вообще не создавать дерево, и если функциональность является общей, я реорганизую ее как обычную процедуру модуля, а затем вызываю ее из каждого объекта. Я вижу преимущество наличия одной единственной точки изменения (например, вместо добавления к Dog, Cat, Moose и т. Д. Я просто добавляю к Animal, что является основным преимуществом наследования), но вы можете достичь того же с помощью цепочка делегирования (например, а-ля JavaScript). Я не утверждаю, что это лучше, просто по-другому.

Я также нашел аналогичный пост по этому поводу.

Ответы [ 11 ]

0 голосов
/ 20 июня 2009

Классы в Python - это просто способы группировки групп функций и данных. Они отличаются от классов в C ++ и т. Д.

В основном я видел наследование, используемое для переопределения методов суперкласса. Например, возможно использование Python'а для наследования будет следующим:

from world.animals import Dog

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

Конечно, кошки не относятся к типу собак, но у меня есть этот (третий) класс Dog, который отлично работает, за исключением метода speak, который я хочу переопределить - это экономит повторная реализация всего класса, просто так мяукает. Опять же, хотя Cat не является типом Dog, но кошка наследует много атрибутов ..

Гораздо лучшим (практичным) примером переопределения метода или атрибута является то, как вы меняете пользовательский агент для urllib. Вы в основном подкласс urllib.FancyURLopener и измените атрибут версии ( из документации ):

import urllib

class AppURLopener(urllib.FancyURLopener):
    version = "App/1.7"

urllib._urlopener = AppURLopener()

Другой способ использования исключений - для исключений, когда наследование используется более «правильным» способом:

class AnimalError(Exception):
    pass

class AnimalBrokenLegError(AnimalError):
    pass

class AnimalSickError(AnimalError):
    pass

.. затем вы можете поймать AnimalError, чтобы перехватить все исключения, которые унаследованы от него, или конкретное, например AnimalBrokenLegError

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...