Теория

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

Гибкость объектно-ориентированного программирования проявляется в наследовании. Так называют возможность на основе существующих классов создавать классы-наследники, изменяя свойства и методы родительских классов, и добавляя новые.
Наследование организует иерархическую структуру проекта. Разработчик определяет классу-родителю основные свойства и методы, к которым можно обращаться в объектах любого из дочерних классов.
image
Синтаксис наследования в Python выглядит так:
Скопировать кодPYTHON
# у класса может быть несколько родительских классов class <Имя нового_класса>(<Имя класса-родителя 1>[, <Имя класса-родителя 2>, ...]): <тело класса>
Пример:
Скопировать кодPYTHON
class User: def __init__(self, name, phone): self.name = name self.phone = phone def show(self): print(f'{self.name} ({self.phone})') # объявляем класс Friend, дочерний по отношению к классу User class Friend(User): def show(self): print(f'Имя: {self.name} || Телефон: {self.phone}') # создаём объекты User и Friend father = User("Дюма-отец", "+33 3 23 96 23 30") son = Friend("Дюма-сын", "+33 3 23 96 23 30")
Класс User — родительский для класса Friend. Все свойства и методы родительского класса наследуются: в объектах класса Friend мы можем обращаться к свойствам name и phone, а также вызывать метод show().
Но в классе Friend метод show сработает иначе, чем в объекте User: этот метод был переопределён, описан заново, и данные на экран выведутся в другом формате.
Скопировать кодPYTHON
# вызываем метод show() класса User (родительского) father.show() # результат: # Дюма-отец (+33 3 23 96 23 30) # вызываем метод show() класса Friend (дочернего) son.show() # результат выглядит иначе, чем у объекта User: # Имя: Дюма-сын || Телефон: +33 3 23 96 23 30
Отношения между двумя этими классами принято описывать так:
  • User является родительским классом для Friend;
  • Friend является дочерним классом для User, или Friend наследуется от User.
Предположим, в объектах класса Friend вы хотите сохранять не только имя и телефон, но и адрес. Значит, при создании экземпляра класса Friend нужно передавать параметр address. Но конструктор родительского класса принимает только name и phone.
В этом случае поможет переопределение родительского конструктора, функции __init__.
Скопировать кодPYTHON
class User: def __init__(self, name, phone): self.name = name self.phone = phone def show(self): print(f'{self.name} ({self.phone})') # наследуем класс Friend от User class Friend(User): # пишем конструктор класса-наследника, чтобы он принимал все нужные параметры def __init__(self, name, phone, address): # наследуем функциональность конструктора из класса-родителя super().__init__(name, phone) # добавляем новую функциональность: свойство address self.address = address # полностью переопределяем родительский метод show() def show(self): print(f'Имя: {self.name} || Телефон: {self.phone} || Адрес: {self.address}')
При создании экземпляра класса Friend будет вызван конструктор класса __init__.
Первым делом в нём вызывается функция super(), в неё передаются значения name и phone. При этом происходит вызов конструктора родительского класса и его функциональность сохраняется в классе-наследнике.
Так можно вызвать не только конструктор, но и любой другой метод родительского класса, ведь конструктор — это тоже метод класса, хоть и немного особенный.
После вызова super() сохраняем значение address в свойство self.address.
Теперь мы можем одинаково обращаться к любому из трех полей, как к унаследованным, так и к добавленному. Например, в методе show() мы можем напечатать адрес друга.
Если в дочернем классе вы хотите сохранить функциональность класса-родителя, то в дочернем классе нужно вызвать функцию super(), как в примере с конструктором.
Если же вы хотите полностью изменить поведение конструктора класса или иного метода, то вызывать super() нет необходимости, в этом случае нужно полностью написать конструктор дочернего класса.
Именно это мы и сделали в примере, но не для конструктора, а для метода show(): мы перезаписали его полностью.

Важные термины

На собеседованиях или при чтении научной литературы потребуется знать основные термины теории ООП.
Интерфейс класса — это функциональная часть класса, через которую происходит взаимодействие с самим классом или с экземпляром этого класса.
Описывая класс, разработчик одновременно создает интерфейс для обращения к этому классу и к его экземплярам. Для класса User интерфейсом будут те части класса или объекта, с которыми может взаимодействовать другой код:
Скопировать кодPYTHON
class Bird: # Это конструктор, он вызывается при создании объекта def __init__(self, name, size): self.name = name self.size = size def show(self): # вызывается для вывода на экран всех свойств объекта # это интерфейс класса, к нему можно обратиться из внешнего кода print(f'{self.name} носит одежду размера «{self.size}».') # Создание объекта sparrow = Bird('Воробей', 'S') # Теперь можно воспользоваться его внешним интерфейсом: методом show() sparrow.show() # Результат: Воробей носит одежду размера «S».
Наследование — способ описать новый класс на базе существующего. При этом в дочернем классе можно сохранить или переопределить свойства и методы родительского класса.
Механизм наследования прозрачен: если функция или свойство родительского класса ещё раз описаны в дочернем классе, то этот метод или это свойство будут переопределены. А остальные свойства и методы будут работать точно так же, как у класса-родителя.
Скопировать кодPYTHON
class Bird: def __init__(self, name, size): # Это конструктор, он вызывается при создании объекта self.name = name self.size = size def show(self): # вызывается для вывода на экран всех свойств объекта print(f'{self.name} носит одежду размера «{self.size}».') class Parrot(Bird): def __init__(self, name, size, sound): super().__init__(name, size) self.sound = sound def show(self): # вызывается для вывода на экран всех свойств объекта print(f'{self.name} носит одежду размера «{self.size}» и {self.sound}.') # Создание объектов sparrow = Bird('Воробей', 'S') ara = Parrot('Попугай ара', 'XL', 'разговаривает') nymphicus = Parrot('Попугай Корелла', 'S', 'щебечет') # Теперь можно воспользоваться его внешним интерфейсом: методом show() sparrow.show() ara.show() nymphicus.show() # Результат: # Воробей носит одежду размера «S». # Попугай ара носит одежду размера «XL» и разговаривает. # Попугай Корелла носит одежду размера «S» и щебечет.
Инкапсуляция — объединение и скрытие методов и свойств, и предоставление доступа к ним через простой внешний интерфейс.
Даже не имея понятия, как работают методы lower, upper или split объекта типа str, мы из документации знаем о них и можем управлять объектом. Методы «инкапсулированы», а разработчику предоставлен интерфейс для их вызова: string.upper() А класс Parrot инкапсулирует свойства попугая name, size, sound, и его метод show(): совершенно необязательно знать, как они работают, можно просто обратиться к ним и получить результат: ara.show()
Полиморфизм — возможность взаимодействовать с объектами разных типов через одинаковые интерфейсы, обращаться к свойствам и методам, общим для всех объектов.
В примере с птичками от класса Bird наследуются классы Parrot и Predator, а от Predator наследуется класс Egg.
К какому бы наследнику класса Bird мы ни обратились через интерфейсы name или show() — мы получим ответ (или, как минимум, не получим ошибку), потому что мы предусмотрительно реализовали принцип полиморфизма: у всех наследников класса Bird есть эти интерфейсы.
Скопировать кодPYTHON
class Bird: def __init__(self, name, size): self.name = name self.size = size def show(self): print(f'{self.name} носит одежду размера «{self.size}».') class Parrot(Bird): def __init__(self, name, size, sound): super().__init__(name, size) self.sound = sound def show(self): print(f'{self.name} носит одежду размера «{self.size}» и {self.sound}.') class Predator(Bird): def __init__(self, name, size, claws_size): super().__init__(name, size) self.claws_size = claws_size def show(self): print(f'{self.name} носит одежду размера «{self.size}» и когти размера {self.claws_size}.') class Egg(Predator): def show(self): print(f'Из яйца вылупится птичка {self.name} размера «{self.size}» с когтями размера {self.claws_size}.')
Задача   3 / 3
1.
Запустите код и убедитесь, что на экран выведены данные одного и того же пользователя, но в разных форматах.
Когда будете готовы, нажимайте «Проверить» и «Далее».
Подсказка
Запустите код и убедитесь, что на экран выведены данные одного и того же пользователя, но в разных форматах.
2.
В коде описан класс Point (точка на карте), у него есть свойства — широта и долгота, и метод distance(self, other) — это расстояние между двумя точками в километрах (параметр other должен получить другой объект Point).
Создайте два класса-наследника класса Point:
  • City(Point, name, population) описывает город, в конструктор передаются координаты города (объект класса Point), его название и численность населения.
  • Mountain(Point, name, height) описывает гору, в конструктор передаются координаты горы (объект класса Point), её название и высота в метрах.
Ваша задача — вывести на экран расстояние от Москвы до Эвереста.
Подсказка
  1. В конструкторе класса City присвойте значения полям self.name и self.population. Затем вызовите конструктор родительского класса с аргументами «широта» и «долгота»: super().__init__(latitude, longitude)
  2. Конструктор класса Mountain должен принять на вход широту, долготу, название горы и её высоту: def __init__(self, latitude, longitude, name, height):
  3. Опишите метод show() в классе Mountain.
3.
Опишите на ООП взаимодействие студента, ментора, код-ревьюера и куратора.
Все эти люди — люди, поэтому создадим базовый класс Human, со свойством name (у каждого человека должно быть имя) и методом answer_question() для ответов на вопросы.
По умолчанию объект Human будет отвечать на любой вопрос так: «Очень интересный вопрос! Не знаю.»
От класса Human унаследуем классы Student, Mentor, CodeReviewer и Curator.
Student должен уметь задавать вопросы. Реализуйте в классе Student метод ask_question(Human, question). При вызове этот метод должен:
  1. Напечатать на экране вопрос в формате <имя человека, которому задаём вопрос>, <текст вопроса>
  2. Задать вопрос question человеку, объекту класса Human. Имя объекта, которому адресован вопрос, передаётся при вызове метода ask_question().
Объекты классов Mentor, CodeReviewer и Curator должны уметь отвечать на вопросы при вызове метода answer_question(). Задан непредусмотренный вопрос — для него подойдет ответ по умолчанию.
После того, как вы допишете код, ваша программа должна вывести на экран такой текст:
Скопировать код
Марина, мне грустненько, что делать? Держись, всё получится. Хочешь видео с котиками? Ира, мне грустненько, что делать? Отдохни и возвращайся с вопросами по теории. Евгений, когда каникулы? Очень интересный вопрос! Не знаю. Евгений, что не так с моим проектом? О, вопрос про проект, это я люблю. Виталя, как устроиться на работу питонистом? Очень интересный вопрос! Не знаю. Ира, как устроиться работать питонистом? Сейчас расскажу.
Подсказка
Чтобы вызвать метод answer_question() у родительского класса, напишите super().answer_question(question)
Код
Результат