Предположим, что у вас следующая ситуация
#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 не знает природы переданного ему Animal. Он просто посылает ему сигнал "speak" и оставляет позднему связыванию заботу о том, какой метод вызвать: либо Cat::speak(), либо Dog::speak(). Это означает, что для makeSpeak знание того, какой подкласс передается, не имеет значения.
Но как быть с Python? Давайте посмотрим код для того же случая на языке Python. Обратите внимание, что я стараюсь на мгновение стать как можно более похожим на случай с Си++:
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 вы можете послать сигнал "speak" любому объекту. Если объект в состоянии с ним справиться, то он будет выполнен, в противном случае возникнет исключение. Предположим, что вы добавили в оба кода класс Airplane и передали объект Airplane в makeSpeak. В случае C++ это не скомпилируется, так как Airplane не является производным классом от Animal. В случае с Python это приведет к возникновению исключения во время выполнения, что может быть даже ожидаемым поведением.
С другой стороны, предположим, что вы добавили класс MouthOfTruth с методом speak(). В случае с Си++ придется либо рефакторить иерархию, либо определить другой метод makeSpeak для приема объектов MouthOfTruth, либо в java извлечь поведение в CanSpeakIface и реализовать интерфейс для каждого. Существует множество решений...
Я хотел бы отметить, что пока не нашел ни одной причины использовать наследование в Python (кроме фреймворков и деревьев исключений, но я предполагаю, что существуют альтернативные стратегии). Вам не нужно реализовывать иерархию, производную от базы, чтобы работать полиморфно. Если вы хотите использовать наследование для повторного использования реализации, вы можете сделать то же самое с помощью сдерживания и делегирования, с тем дополнительным преимуществом, что вы можете изменять его во время выполнения, и вы четко определяете интерфейс сдерживаемого, не рискуя получить непредвиденные побочные эффекты.
Таким образом, вопрос остается открытым: в чем смысл наследования в Python?
Редактирование: спасибо за очень интересные ответы. Действительно, наследование можно использовать для повторного использования кода, но я всегда осторожен при повторном использовании реализации. В целом, я стараюсь делать очень мелкие деревья наследования или не делать их вообще, а если функциональность является общей, я рефакторю ее как общую модульную процедуру и затем вызываю ее из каждого объекта. Я, конечно, вижу преимущество в наличии одной точки изменения (например, вместо добавления к Dog, Cat, Moose и т.д. я просто добавляю к Animal, что является основным преимуществом наследования), но того же самого можно добиться с помощью цепочки делегирования (например, как в JavaScript). Я не утверждаю, что это лучше, просто это другой способ.
Я также нашел похожую статью на эту тему.
Вы называете "утиную" типизацию во время выполнения программы "переопределением"- наследования, однако я считаю, что наследование имеет свои достоинства как подход к проектированию и реализации, являясь неотъемлемой частью объектно-ориентированного проектирования. По моему скромному мнению, вопрос о том, можно ли добиться чего-то другого, не очень актуален, поскольку на самом деле можно писать на Python без классов, функций и прочего, но вопрос в том, насколько хорошо спроектированным, надежным и читаемым будет ваш код.
Я могу привести два примера, в которых наследование, на мой взгляд, является правильным подходом, но я уверен, что их гораздо больше.
Во-первых, при грамотном коде ваша функция makeSpeak может захотеть проверить, что ее входные данные действительно являются животным, а не только то, что "оно умеет говорить", и в этом случае наиболее элегантным методом будет использование наследования. Опять же, это можно сделать и другими способами, но в этом и состоит прелесть объектно-ориентированного проектирования с наследованием - ваш код будет "действительно" проверять, является ли входной сигнал "животным".
Второй, и, очевидно, более простой, является инкапсуляция - еще одна неотъемлемая часть объектно-ориентированного проектирования. Это становится актуальным, когда у предка есть члены данных и/или неабстрактные методы. Возьмем следующий глупый пример, в котором предок имеет функцию (speak_twice), вызывающую тогдашнюю абстрактную функцию:
class Animal(object):
def speak(self):
raise NotImplementedError()
def speak_twice(self):
self.speak()
self.speak()
class Dog(Animal):
def speak(self):
print "woff!"
class Cat(Animal):
def speak(self):
print "meow"
Если предположить, что функция "speak_twice" является важной, то вы не захотите кодировать ее ни в Dog, ни в Cat, и я уверен, что вы можете экстраполировать этот пример. Конечно, можно реализовать на Python отдельную функцию, которая будет принимать некоторый объект типа duck, проверять, есть ли у него функция speak, и вызывать ее дважды, но это и неэлегантно, и упускает пункт номер 1 (подтверждение того, что это животное). Хуже того, чтобы усилить пример инкапсуляции, что если функция-член в классе-потомке захочет использовать
"speak_twice"`?
Все становится еще более понятным, если в классе-предке есть член данных, например "number_of_legs"
, который используется неабстрактными методами предка, например "print_number_of_legs"
, но инициализируется в конструкторе класса-потомка (например, Dog инициализирует его значением 4, а Snake - значением 0).
Опять же, я уверен, что примеров можно привести бесконечно много, но, по сути, любое (достаточно крупное) программное обеспечение, основанное на надежном объектно-ориентированном дизайне, будет требовать наследования.
Наследование в Python-это все о повторном использовании кода. Разложим общую функциональность в базовый класс и реализовать различные функции в производных классах.
Наследование в Python-это больше удобство, чем все остальное. Я считаю, что это'ы лучше всего использовать, чтобы обеспечить класс с "по умолчанию&.и"
Действительно, существует значительное сообщество разработчиков на языке Python, которые выступают против использования наследования вообще. Все что вы делаете, Дон'Т просто Дон'т переборщить. Без излишне сложной иерархии классов-это верный способ получить помечены как "Java-программист", и вы просто можете'т иметь, что. :-)
Я думаю, что смысл наследования в Python заключается не в том, чтобы код компилировался, а в том, чтобы расширить класс на другой дочерний класс и переопределить логику базового класса. Однако утиная типизация в Python делает концепцию "интерфейса" бесполезной, поскольку вы можете просто проверить существование метода перед его вызовом, и вам не нужно использовать интерфейс для ограничения структуры класса.
Я думаю, что это очень трудно дать содержательное, конкретный ответ с такими абстрактными примерами...
Для упрощения, существует два вида наследования: интерфейса и реализации. Если вам нужно наследовать реализацию, тогда Python не так уж и отличается от статически типизированных ОО языки, такие как C++.
Наследование интерфейса, где есть большая разница, с фундаментальными последствиями для дизайна вашего программного обеспечения в моем опыте. Языков, таких как Python не заставит вас использовать наследование в этом случае, и избежать наследования является положительным моментом, в большинстве случаев, потому что это очень трудно исправить неправильный выбор конструкции позже. Что'ы известный вопрос, поднятый в любой хорошей книге ООП.
Бывают случаи, когда использовать наследование для интерфейсов, желательно в Python, например плагины и т. д.... Для тех случаев, Python 2.5 и ниже не хватает и "встроенный" и элегантный подход, и несколько больших рамок собственных решений (синец, проф, твистер). Python 2.6 и выше АБВ классы, чтобы решить это.
В C++/Ява/и т. д., полиморфизм возникает по наследству. Отказаться от того, что ублюдочное вера, и динамических языков, открытые до вас.
По сути, в Python нет интерфейс так как "понимание того, что определенные методы вызываются и". Довольно сомнительное и академическое звучание, нет? Это означает, что вас называют "говорить" Вы четко уверены, что этот объект должен быть на "поговорить" по способу. Простой, да? Это очень Лисков-Ян в том, что пользователи класса определяют интерфейс, хороший дизайн-концепции, что приводит вас в здоровой ТДД.
Поэтому то, что осталось, так как другой плакат вежливо удалось избегать слов, разделяя код трик. Вы могли бы написать такое же поведение в каждой "на ребенка" класс, но это было бы излишним. Легче наследовать или микс-в функции, инвариантные по иерархии наследования. Мелкие, сухие-Эр код-это лучше в целом.
Это's не наследство, что утка-типирование делает бессмысленно, это'интерфейсы S — как тот, который вы выбрали при создании всех абстрактный класс животное.
Если вы пользовались классом животных, которые вносят некоторую реальном поведении для своих потомков, чтобы использовать, то собака и кошка-классы, которые ввели некоторые дополнительные поведения был бы повод для обоих классов. Это's только в случае класс-предок войска не фактический код в классах-наследниках, что ваш аргумент является правильным.
Потому что Python может непосредственно знать возможности любого предмета, и, поскольку эти возможности изменяемы за пределами определения класса, идея использования чисто абстрактный интерфейс, чтобы "Не говори" в программе какими методами можно назвать несколько бессмысленно. Но, что's не единственным, или даже главным, точки наследования.
Вы можете обойти наследование в Python, и почти любой другой язык. Это's Все о повторное использование кода и упрощение кода.
Просто семантический трюк, но после построения и базовые классы, вы Дон'т даже знаю, что's возможные с вашим объектом, чтобы увидеть, если вы можете сделать это.
Скажем, у вас есть D, который является собака, которая подкласс животных.
command = raw_input("What do you want the dog to do?")
if command in dir(d): getattr(d,command)()
Если бы пользователь ввел в наличии, код будет выполняться правильный метод.
С помощью этого вы можете создать любую комбинацию из млекопитающих/рептилий/птиц гибридное чудовище, которые вы хотите, и теперь вы можете сделать это сказать 'Барк!' во время полета и торчит раздвоенный язык и он будет обрабатывать это правильно! Весело с ним!
Я не'т вижу особого смысла в наследство.
Каждый раз, когда я когда-либо использовал наследование в реальных системах, как меня спалили, потому что это привело к паутине зависимостей, или я просто понял вовремя, что мне будет намного лучше без него. Теперь, я избегаю его как можно больше. Я просто никогда не пользовались этим.
class Repeat:
"Send a message more than once"
def __init__(repeat, times, do):
repeat.times = times
repeat.do = do
def __call__(repeat):
for i in xrange(repeat.times):
repeat.do()
class Speak:
def __init__(speak, animal):
"""
Check that the animal can speak.
If not we can do something about it (e.g. ignore it).
"""
speak.__call__ = animal.speak
def twice(speak):
Repeat(2, speak)()
class Dog:
def speak(dog):
print "Woof"
class Cat:
def speak(cat):
print "Meow"
>>> felix = Cat()
>>> Speak(felix)()
Meow
>>> fido = Dog()
>>> speak = Speak(fido)
>>> speak()
Woof
>>> speak.twice()
Woof
>>> speak_twice = Repeat(2, Speak(felix))
>>> speak_twice()
Meow
Meow
Джеймс Гослинг однажды спросили на пресс-конференции вопрос по аналогии: "Если бы вы могли вернуться назад и сделать Ява по-другому, что бы ты уйти?&и". Его ответ был, что "классы" и, в которых был смех. Однако, он был серьезным и пояснил, что действительно, не было классов, были проблемы, но наследство.
Я рассмотреть это как зависимость от наркотиков - это позволяет быстро исправить, что чувствует себя хорошо, но в конце концов, как на крюке. Под этим я подразумеваю, что это удобный способ повторного использования кода, но сил нездоровая связь между ребенком и родителем класса. Изменения родителей может сломать ребенка. Ребенок зависит от родителя определенные функции и не могут изменить эту функциональность. Поэтому функциональные возможности ребенка и привязан к родителю - может быть только обе.
Лучше-обеспечить единый клиентский класс Перед для интерфейса, который реализует интерфейс, используя функции других объектов, которые составляются во время строительства. Делаю это через правильно разработанные интерфейсы, все соединения могут быть исключены, и мы предоставляем широкие возможности комбинирования с API (в этом нет ничего нового - большинство программистов уже этого просто не хватает). Обратите внимание, что класс реализации должен не просто предоставлять функциональность, в противном случае клиент должен просто использовать в составе классов напрямую - он должен сделать что-то новое, комбинируя эти возможности.
Есть аргумент из лагеря наследования, что чисто реализаций делегация страдают, потому что они требуют много 'клей' методы, которые просто передать значения через делегацию 'сеть'. Однако, это просто изобретать наследство-как дизайн, используя делегации. Программисты со слишком многих лет воздействия на наследство на основе конструкции являются особенно уязвимыми для попадания в эту ловушку, так как, не осознавая этого, они будут думать, как они будут выполнять то, используя наследование, а затем преобразовать их к передаче.
Правильное разделение обязанностей, как приведенный выше код не't, требуют наличия клея методов, так как каждый шаг есть на самом деле добавленной стоимости, так они на самом деле не 'клей' методы (если они Дон'т добавить стоимость, дизайн Недостатки).
Он сводится к следующему:
Для повторно используемого кода, каждый класс должен делать только одну вещь (и это хорошо).
Наследование создание классов, что делать больше чем одну вещь, потому что они перепутал с родительских классов.
Таким образом, используя наследование делает классы, которые трудно использовать.
Еще один небольшой момент заключается в том, что ОП'ы 3'РД примеру, вы можете'т позвонить isinstance(). Например, передавая свою 3'РД примеру на другой объект, который принимает и "животное" и введите призывы говорить на нем. Если вы это делаете Дон'т вы должны проверить тип собака, Тип КПП и так далее. Не уверен, если проверить экземпляр действительно на "обновления" и, из-за позднего связывания. Но тогда вам придется реализовать какой-то способ, что AnimalControl не'т попробовать, чтобы бросить видах Чизбургер в грузовике, потому что чизбургеры Дон'т сказать.
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 - это, по сути, просто способ группировки функций и данных. Они отличаются от классов в C++ и подобных.
В основном я видел, как наследование используется для переопределения методов суперкласса. Например, возможно, более подходящее для Python использование наследования было бы...
from world.animals import Dog
class Cat(Dog):
def speak(self):
print "meow"
Конечно, кошки не являются разновидностью собак, но у меня есть класс Dog
(сторонний), который прекрасно работает, за исключением метода speak, который я хочу переопределить - это позволит не переделывать весь класс, чтобы он мяукал. Опять же, хотя
Catне является типом
Dog`, но кошка наследует многие атрибуты...
Гораздо более удачным (практическим) примером переопределения метода или атрибута является способ изменения user-agent для urllib. В основном вы подклассифицируете urllib.FancyURLopener
и изменяете атрибут version (из документации):
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
.