Какой смысл наследования в Python?


Answers

Наследование в Python - все о повторном использовании кода. Факторизуйте общую функциональность в базовый класс и реализуйте различные функции в производных классах.

Question

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

#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, что является основным преимуществом наследования), но вы можете достичь того же с цепочка делегирования (например, a la JavaScript). Я не утверждаю, что это лучше, но это еще один способ.

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




Я думаю, что точка наследования в Python - это не компиляция кода, а истинная причина наследования, которая расширяет класс до другого дочернего класса и переопределяет логику в базовом классе. Однако утка, набирая в Python, делает концепцию «интерфейса» бесполезной, потому что вы можете просто проверить, существует ли метод до вызова, без необходимости использовать интерфейс для ограничения структуры класса.




Еще одна небольшая точка - это 3-й пример op, вы не можете назвать isinstance (). Например, передав ваш третий пример другому объекту, который принимает и тип «Animal», на нем говорят разговоры. Если вы это сделаете, вам не придется проверять тип собаки, тип кошки и т. Д. Не уверен, что проверка экземпляра действительно «Pythonic» из-за позднего связывания. Но тогда вам придется реализовать какой-то способ, которым AnimalControl не пытается бросать типы Cheeseburger в грузовик, потому что Cheeseburgers не говорят.

class AnimalControl(object):
    def __init__(self):
        self._animalsInTruck=[]

    def catachAnimal(self,animal):
        if isinstance(animal,Animal):
            animal.speak()  #It's upset so it speak's/maybe it should be makesNoise
            if not self._animalsInTruck.count <=10:
                self._animalsInTruck.append(animal) #It's then put in the truck.
            else:
                #make note of location, catch you later...
        else:
            return animal #It's not an Animal() type / maybe return False/0/"message"



Вы можете обойти наследование в Python и практически на любом другом языке. Все дело в повторном использовании кода и упрощении кода.

Просто семантический трюк, но после создания классов и базовых классов вам даже не нужно знать, что возможно с вашим объектом, чтобы увидеть, можете ли вы это сделать.

Скажем, у вас есть d, который является Собакой, которая подклассифицирует Animal.

command = raw_input("What do you want the dog to do?")
if command in dir(d): getattr(d,command)()

Если какой бы то ни было пользователь, набранный пользователем, код будет работать надлежащим образом.

Используя это, вы можете создать любую комбинацию гибридного монстра Mammal / Reptile / Bird, которую вы хотите, и теперь вы можете заставить ее сказать «Bark!» во время полета и высунув свой раздвоенный язык, и он справится с этим правильно! Получайте удовольствие от этого!




В C ++ / Java / etc полиморфизм вызван наследованием. Отказаться от этой ошибочной веры, а динамические языки открываются вам.

По сути, в Python нет интерфейса так же, как «понимание того, что определенные методы вызываемы». Довольно ручная, волнительная и академическая, нет? Это означает, что, поскольку вы называете «говорить», вы явно ожидаете, что объект должен иметь метод «говорить». Просто, да? Это очень Liskov-ian в том, что пользователи класса определяют его интерфейс, хорошую концепцию дизайна, которая ведет вас к более здоровому TDD.

Итак, что осталось, так как другой плакат вежливо сумел не сказать, трюк для обмена кодами. Вы можете написать одно и то же поведение в каждом «дочернем» классе, но это будет излишним. Легче наследовать или смешивать функции, которые инвариантны по иерархии наследования. Меньший код DRY-er лучше вообще.






Related