В программировании мы часто сталкиваемся с необходимостью создать новый класс, который расширяет или изменяет функциональность уже существующих классов. Эту задачу можно выполнить при помощи наследования классов — одного из ключевых механизмов объектно-ориентированного программирования.
В этом уроке мы рассмотрим, как работает наследование классов, и как его можно применять для создания новых сложных структур и при этом не дублировать код.
Наследование классов
Наследование классов — механизм, который позволяет создавать классы (подклассы) на основе других классов — базовые или суперклассы. Подклассы наследуют структуру базовых классов — получают возможность использовать всё, что определено в базовом классе.
Наследование в Python — сложная система со множеством нюансов и деталей. Мы будем изучать его в несколько этапов на протяжении всего курса.
Рассмотрим наследование на примере структуры HTML. Каждый тег в HTML по своему уникален. Также у них есть общие атрибуты и некоторые другие характеристики. Попробуем отобразить это с помощью иерархии классов:
class HTMLElement:
def __init__(self, attributes=None):
self.attributes = attributes if attributes else {}
self.body = None
def set_attribute(self, key, value):
self.attributes[key] = value
def get_attribute(self, key):
return self.attributes.get(key)
def set_text_content(self, body):
self.body = body
def get_text_content(self):
return self.body
def stringify_attributes(self):
# Строим: key="value" key2="value2"
pass
В этом коде мы определили базовый класс HTMLElement
, который представляет общую структуру HTML-элемента. Методы set_attribute
и get_attribute
устанавливают и получают атрибуты HTML-элемента, а set_text_content
и get_text_content
работают с текстовым содержимым элемента.
Теперь создадим подкласс HTMLAnchorElement
, представляющий тег «a» в HTML, наследуя его от класса HTMLElement
:
class HTMLAnchorElement(HTMLElement):
def __str__(self):
attr_line = self.stringify_attributes()
body = self.get_text_content()
return f'<a{attr_line}>{body}</a>'
Здесь мы определили класс HTMLAnchorElement
, который наследует все атрибуты и методы класса HTMLElement
. Мы также добавили метод __str__
, который возвращает строковое представление HTML-элемента.
Наследование записывается так: A(B)
. Эта запись означает, что класс A наследует класс B.
Теперь посмотрим, как работает наследование:
# Инициализация экземпляра суперкласса
anchor = HTMLAnchorElement({'href': '<https://ru.hexlet.io>'})
anchor.set_text_content('Hexlet')
print(f"Anchor: { anchor }") # __str__ вызывается автоматически
# => Anchor: <a href="<https://ru.hexlet.io>">Hexlet</a>
Внутри HTMLAnchorElement
нет определения конструктора, но благодаря наследованию, этот класс имеет доступ ко всем публичным методам и свойствам суперкласса. Python вызывает их автоматически при обращении к ним. В свою очередь, внутри __str__()
вызываются методы, которых нет в текущих классах, поэтому они также берутся из родительского класса.
Создание подкласса на основе одного класса является довольно простым процессом. Но что, если возникает необходимость наследовать свойства сразу от нескольких классов? В Python для этих целей существует механизм множественного наследования, который позволяет создавать подкласс на основе нескольких классов.
Цепочка наследования
В отличие от некоторых других языков программирования, наследование классов в Python — одиночное и множественное. То есть в Python можно наследоваться от одного или нескольких классов сразу.
Это свойство языка добавляет гибкости, но также и сложности, особенно, когда у базовых классов есть методы с одинаковыми именами. При этом цепочка наследования может быть сколь угодно глубокой:
class D: pass
class C(D): pass
class B(C, D): pass
class A(B): pass
Это пример четырех классов, каждый из которых наследуется от предыдущего. Это показывает, что наследование в Python может быть «цепочкой», где каждый новый класс наследует свойства и методы предыдущего.
Когда у нас много классов и сложная структура наследования, может возникнуть вопрос, как определить, к какому классу принадлежит конкретный объект. Для этих целей предоставляются специальные операторы и функции для проверки типов.
Оператор проверки типа
В Python можно использовать оператор isinstance()
для проверки принадлежности объекта к определенному классу, учитывая при этом наследование:
anchor = HTMLAnchorElement({'href': '<https://ru.hexlet.io>'})
anchor.set_text_content('Hexlet')
print(isinstance(anchor, HTMLElement)) # => True
Здесь мы создали объект anchor
класса HTMLAnchorElement
и проверили, является ли он экземпляром класса HTMLElement
. Поскольку HTMLAnchorElement
наследуется от HTMLElement
, функция isinstance()
возвращает True
.
Для проверки точного класса объекта, мы можем использовать функцию type()
:
print(type(anchor) == HTMLAnchorElement) # => True
Здесь мы используем функцию type()
, чтобы проверить, что класс объекта anchor
точно является HTMLAnchorElement
.
Подобные проверки могут ограничивать возможность использования полиморфизма. Иногда без них не обойтись, но в большинстве случаев лучше использовать интерфейс объекта.
Выводы
Python предоставляет возможность использовать наследование для создания сложных структур данных и гибкого кода. Однако при множественном наследовании и проверках типов могут возникнуть трудности. Вместо этого, рекомендуется использовать интерфейс объекта, чтобы реализовать принцип полиморфизма и избежать этих проблем.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.