Что делать, если предметы похожи? Мы подробно обсудим все необходимые концепции и их реализацию. Я предполагаю, что у вас есть базовые знания о Python и ООП. Даже если вы никогда не слышали о наследовании или работали с ним, это руководство должно быть вам полезно.

Наследование

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

Итак, оба класса Person и Employee имеют много похожего кода, когда при программировании вы обнаруживаете, что дублируете код вы делаете неправильно.

Поэтому, когда объекты очень похожи друг на друга, чтобы следовать принципу DRY (Do not Repeat Yourself), мы используем наследование (но не всегда).

Как следует из названия, наследование относится к наследованию класса (то есть его атрибутов и методов) другим классом. Так же, как вы унаследовали цвет волос или глаза от своих родителей.

Класс, свойства которого наследуются, называется родительским классом (или суперклассом), тогда как наследующий класс называется дочерним классом.

Мы используем наследование, когда у нас есть связь «есть» между объектами. Мол, сотрудник - это Человек, машина - это средство передвижения, собака - это животное.

Используя наследование в нашем предыдущем примере, мы имеем:

Итак, что мы сделали?

  • Чтобы сделать класс Employee дочерним классом (или подклассом) класса Person, мы передали Person в качестве параметра сотруднику. Так мы наследуем один класс от другого.
  • Для доступа к атрибутам и методам родительского класса мы используем метод super () .
  • Мы знаем, что оба объекта похожи, но не одинаковы. Вот почему мы расширили методы родительского класса __init__ в дочернем классе Employee, чтобы добавить employee_id.
  • Поскольку нам не нужно было изменять метод имени, мы не переопределяли и не расширяли его. Мы можем получить прямой доступ к методу имени из объекта Сотрудник.

Это базовый пример наследования, нет проблем, если вы не получили все по ходу обучения.

Все передается по наследству

Возможно, вы знаете, что все в Python - это объект. Также все классы по умолчанию наследуются от встроенного класса Object неявно (писать его не нужно).

Переопределение атрибутов и методов

Что ж, override.py говорит обо всем.

Расширяющие методы

В предыдущих примерах мы расширили наш метод __init__. Давай узнаем об этом больше.

Результатом следующего кода будет

bow bow
meow meow

То же самое и с реальным миром: и кошка, и собака говорят, но по-разному.

Это очень простой пример расширения метода. Метод __init__ - распространенный вариант использования расширения метода. В отличие от переопределения метода, мы можем использовать как логику / код родительского класса, так и дополнительный код, требуемый дочерним классом, путем расширения метода. Спасибо super ().

Множественное наследование

Класс может наследовать свойства более чем одного класса. Возможно, вы унаследовали волосы от отца и глаза от матери.

Простой пример множественного наследования:

Итак, я не знаю почему, но мы создаем робота, похожего на собаку, поэтому нам нужно унаследовать его от Робота и Класс собак.

Мы должны использовать ‹class_name› .__ init__ для инициализации нашего класса RoboDog с обоими родительскими классами (Robot и Dog) атрибуты и методы.

Итак, у нас есть способ ссылаться на оба родительских класса, но как насчет методов. Поскольку и Робот, и Собака имеют метод walk (), который будет использовал?

Когда мы ищем атрибут в классе, который участвует в множественном наследовании Python, соблюдается порядок. Сначала он ищется в текущем классе. Если не найден, поиск переходит к родительским классам. Он следует слева направо или в глубину. например в классе RoboDog робот передается первым, поэтому он больше слева, чем Dog, поэтому он упорядочивается перед классом Dog.

Этот порядок называется порядком разрешения методов (MRO). В нашем предыдущем примере порядок следующий:

  1. РобоСобака
  2. Робот
  3. Собака
  4. Объект (встроенный класс python, все классы унаследованы от)

т.е. вывод кода:

I am walking using my wheels

Но что, если по какой-то безумной причине вы хотите использовать метод walk () класса Dog для объекта RoboDog, но не хотите изменять порядок наследования?

Просто используйте это:

Dog.walk(Pika)

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

Теперь мы рассмотрим типичную проблему с множественным наследованием и то, как Python решает ее.

Алмазная проблема

Эта проблема возникает, когда класс наследуется от двух базовых классов, которые сами унаследованы от класса.

Ниже приводится простой пример наследования Diamond.

Выход:

I am class C

Пока это выглядит хорошо. Но здесь есть проблема. Если мы хотим использовать методы родительского класса, у нас возникает проблема. Видеть

Вывод следующего кода:

I am Superclass
I am ClassA
I am Superclass
I am ClassB
I am ClassC
2 1 1 1

Итак, вы можете видеть, что Суперкласс вызывается дважды. Причина довольно проста: и ClassA, и ClassB вызывают Superclass соответственно.

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

Теперь очевидно, что есть обходной путь. Но в большинстве объектно-ориентированных языков нам приходится писать сложный код. Я не буду вдаваться в подробности обходных путей, потому что у python есть довольно простое решение этой проблемы с помощью своего метода Super. ну буквально метод super ().

I am Superclass
I am ClassB
I am ClassA
I am ClassC
1 1 1 1

Итак, простой метод решил проблемы множественного наследования. Это показывает, насколько хорошо спроектирован питон. Но как? Как метод python или super () решает, что и когда вызывать. Помните MRO, о котором мы говорили ранее? Ага, это снова нам помогло. Он основан на алгоритме «линеаризации суперкласса C3». Это называется линеаризацией, потому что древовидная структура разбита на линейный порядок.

Он преобразует дерево наследования в список. Мы не будем вдаваться в подробности этого алгоритма в этом руководстве, но вы можете просмотреть список, используя:

print(ClassC.mro())

Выход:

[<class '__main__.ClassC'>, 
<class '__main__.ClassA'>, 
<class '__main__.ClassB'>, 
<class '__main__.Superclass'>, 
<class 'object'>]

Итак, сначала ClassC вызывает ClassA, затем ClassA (а не суперкласс) вызывает ClassB, а затем ClassB вызывает суперкласс.

Алгоритм использует метод next для вызова классов соответственно. Кроме того, ClassC сначала вызывает ClassA, потому что ClassA сначала передается ClassC.

Множественное наследование против композиции

Хотя в Python легко использовать множественное наследование, это все же сложная концепция. Очень часто с этим сложно справиться, нужно запомнить все дерево наследования или тратить время на поиски, что откуда взялось?

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

Тем не менее, это очень субъективно и зависит от использования. И наследование, и составление наших основ ООП.

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

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

- Зед Шоу (трудный путь изучения питона)

Широко распространенное предубеждение, что множественное наследование является чем-то «опасным» или «плохим», в основном подпитывается языками программирования с плохо реализованными механизмами множественного наследования и, прежде всего, их неправильным использованием.

- Бернд Кляйн (python-courses.eu)

Как показывает опыт, если вы думаете, что вам нужно множественное наследование, вы, вероятно, ошибаетесь, но если вы знаете, что оно вам нужно, вы, вероятно, правы.

- Дасти Филлипс (объектно-ориентированное программирование Python 3)

Состав

Я знаю, что это руководство посвящено наследованию, но мы собираемся обсудить это наиболее конкурирующую композицию, потому что оба паттерна / концепции пытаются достичь того же.

Как правило, мы используем композицию в отношениях «имеет» или «частично из». Например, В книге есть глава, в машине есть бак, шина - часть автомобиля.

Идея Composition довольно проста: создайте объект класса A внутри класса B и используйте этот объект для доступа к свойствам класса A внутри класса B. Следующий пример прояснит это:

Выход:

Chapter 1: Welcome is taken from Best Book by RD from RD-pubs
RD
Best Book by RD from RD-pubs

Все, что мы сделали, это создали объект Book (book_obj) внутри класса Chapter. Затем мы можем получить доступ к атрибутам и методам класса Book с помощью объекта класса Chapter (first_chap).

Здесь следует отметить одну вещь: если мы удалим наш объект chapter, first_chap объект книги также будет удален. В некоторых случаях это оправдано, но что, если вы удалите first_chap из своей Лучшей книги, чтобы добавить еще одну главу, скажем, zero_chap. У нас есть агрегирование, которое позаботится об этом.

Агрегирование

Агрегация очень похожа на композицию, разница заключается скорее в создании объекта класса A внутри класса B, мы сначала создаем объект класса A отдельно, а затем передаем его методу __init__ класса B. Нравится:

Теперь, даже если мы удалим first_chap, у нас все еще есть наш book_obj, чтобы добавить к нему еще один объект Chapter.

Использование композиции вместо множественного наследования

Что ж, до сих пор мы использовали композицию для связи имеет. Давайте воспользуемся этим как альтернативой множественному наследованию.

Возвращаясь к нашему примеру RoboDog, собака и робот являются частью характеристик RoboDog или RoboDog имеет характеристики роботов и собак.

Выход:

I am walking using my wheels
Bow Bow
Serve my Master

Композиция снижает сложность кода и упрощает управление.

Но я снова скажу, что композиция против множественного наследования по-прежнему зависит от ваших потребностей. Конечно, композиция упрощает задачу, но в некоторых случаях вам нужно использовать множественное наследование.

Абстрактные базовые классы

Абстрактный базовый класс (или ABC, потому что нам нравится аббревиатура из трех слов) - это просто класс, который просто представляет абстрактную идею, а не ее реализацию. Класс ABC подобен шаблону для других классов, которым они могут следовать. Экземпляр / объект класса ABC не может быть создан, и он не инициализируется, также подклассы должны реализовывать методы класса ABC. Не волнуйтесь, следующий пример прояснит это.

Выход:

25

Допустим, мы создаем программное обеспечение, связанное с математикой, в частности, что-то, что имеет дело с формами. Мы создали класс Shape и определили в нем несколько методов. Поскольку разные формы имеют разные свойства, мы не реализовали эти методы, а просто передали их подклассам, таким как Square.

Причина, по которой мы это делаем, - убедиться, что все экземпляры (объекты) класса Shape имеют методы площади и периметра. Но это не абстрактный базовый класс. Потому что мы можем создать объект Shape, который не имеет смысла и не соответствует определению классов ABC. Также у класса Square не было метода периметра, но он работает.

Итак, как мы можем реализовать класс ABC? ну, на самом деле Python не поддерживал абстрактный класс, но у него есть модуль, который это делает. Этот модуль по понятным причинам называется abc.

Как сделать урок азбуки?

Нам нужно импортировать класс ABC и декоратор abstractmethod из модуля abc, а затем унаследовать требуемый класс от ABC (Shape в нашем примере) и используйте декоратор abstractmethod для методов, которые вы хотите -класс для реализации.

Вам нужен хотя бы один абстрактный метод в классе ABC, не все методы должны быть абстрактными. Используйте абстрактный метод, оформленный только для методов, которые должны быть реализованы подклассами (например, класс Square в нашем примере должен иметь методы площади и периметра).

Теперь давайте сделаем класс Shape абстрактным базовым классом.

Выход

...
shape_obj = Shape()
TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter

Смотрите, у нас возникла ошибка, потому что мы не можем создать объект класса ABC. (shape_obj = shape () сейчас недействителен).

Даже если мы удалим shape_obj = shape () из файла shape_is_abc.py, мы получим эту ошибку

square_obj = Square(5)
TypeError: Can't instantiate abstract class Square with abstract methods perimeter

потому что у нас есть два абстрактных метода area и perimeter, но только один реализован в классе Square.

Правильный код будет:

Выход:

25
20

Заключение

Мы поговорили почти обо всех основных концепциях, связанных с наследованием. Спасибо, что прочитали все. Цель этого руководства - объяснить концепции, поэтому я использовал простые примеры, которые могут не походить на реальные программы. Но этот туториал определенно поможет вам с наследованием, по крайней мере, я на это надеюсь.

Источники:

Некоторые источники я использовал в качестве справочника, и вы можете использовать их, чтобы узнать больше о наследовании, ООП и Python в целом. :

Python-Courses.eu

Объектно-ориентированное программирование Python 3 от Дасти Филлипса

Youtube канал ProgrammingKnowledge

"Переполнение стека"

Programiz.com

Изучение Python с трудом, Зед Шоу

Вы можете подписаться на меня или поговорить со мной по адресу:

Мой аккаунт в твиттере (он очень новый)

Моя учетная запись Github

Вы можете поделиться своими ценными отзывами или запросами в комментариях ниже, и, если хотите, вы можете подписаться на этот блог Medium, чтобы увидеть больше сообщений, связанных с python, Django или программированием в целом.

Спасибо!