Все классы, которые мы рассматривали до этого, создавались "с нуля". И до тех пор, пока описываемые классами сущности мало похожи друг на друга, создание абсолютно новых классов работает отлично. Но что делать, если мы хотим, чтобы пара классов содержала один и тот же метод — не одноимённый, а именно копию?
Конечно же, мы можем при объявлении класса вместо объявления метода по месту поместить в атрибут ссылку на существующую функцию. И это даже сработает! Но когда таковых методов станет несколько, уследить за тем, что и куда копируется, станет очень сложно. К счастью, есть способ лучше!
Языки, реализующие инструментарий для объектно ориентированного программирования, использующий классы, как правило предоставляют и механизм наследования. Python — один из таких языков. Поэтому классы в Python можно наследовать.
Когда один класс становится наследником другого, то все атрибуты класса-предка (надкласса, superclass) становятся доступны классу-потомку (подклассу, subclass) — наследуются (достаются в наследство).
Наследование позволяет выделить общее для нескольких классов поведение и вынести его в отдельную сущность. То есть наследование является средством переиспользования кода (code reuse) — использования существующего кода для решения новых задач!
Наследование позволяет получить новый класс, немного отличающийся от старого. При этом нам не нужно иметь доступ к коду исходного класса, а значит с помощью наследования мы можем адаптировать под наши задачи (использовать повторно!) в том числе и чужие классы!
Как обычно, рассмотрим пример:
# этот класс у нас уже был
class Counter:
def __init__(self):
self.value = 0
def inc(self):
self.value += 1
def dec(self):
self.value -= 1
# А этот класс - новый. Унаследованный от Counter
class NonDecreasingCounter(Counter): # в скобках указан класс-предок
def dec(self):
pass
Если мы выполним эти объявления классов и посмотрим на поведение экземпляра NonDecreasingCounter
, то увидим, что он работает как Counter
— имеет те же методы и атрибуты (правда, при вызове метода .dec
новый счётчик не изменяет текущее значение):
>>> n = NonDecreasingCounter()
>>> n.inc()
>>> n.inc()
>>> n.value
2
>>> n.dec()
>>> n.value
2
В объявлении NonDecreasingCounter
присутствует метод dec
, а вот откуда взялись value
и inc
? Они были взяты от предка — класса Counter
! Данный факт даже можно пронаблюдать:
>>> n.dec
<bound method NonDecreasingCounter.dec of <__main__.NonDecreasingCounter object at 0x7f361b29c940>>
>>> n.inc
<bound method Counter.inc of <__main__.NonDecreasingCounter object at 0x7f361b29c940>>
Метод dec
— метод класса NonDecreasingCounter
, связанный с конкретным экземпляром NonDecreasingCounter
. А вот inc
— метод класса Counter
, хоть и связанный с всё тем же экземпляром класса-потомка.
Здесь вы можете увидеть сходство с взаимоотношениями между классом и его экземпляром: если экземпляр получает свой собственный атрибут, то этот атрибут заменяет атрибут класса. Точно так же объявления в классе-потомке заменяют собой атрибуты класса-предка, если имя используется то же самое — говорят, переопределяют (override).
И, как и в случае с объектом, который может использовать всё содержимое класса и заменять только небольшую часть атрибутов (или добавлять новые!), так и потомок по-умолчанию получает все атрибуты предка, часть из которых может изменить.
super()
Представим, что нас в целом устраивает класс Counter
из предыдущего примера, но мы хотим при вызове inc
увеличивать значение дважды. Мы могли бы в потомке заменить весь метод и делать внутри нового метода self.value += 2
. Но если бы позже что-то поменялось в исходном классе Counter
, то эти изменения не коснулись бы нашего метода.
Получается, что нам внутри метода потомка нужно получить доступ к методу предка. Методу с тем же именем! Если мы просто обратимся к self.inc
, то получим ссылку на новый метод, ведь мы его переопределили.
Тут нам на помощь приходит специальная функция super
:
class DoubleCounter(Counter):
def inc(self):
super().inc()
super().inc()
Вызов super
здесь заменяет обращение к self
. При этом вы фактически обращаетесь к "памяти предков": получаете ссылку на атрибут предка. Более того, что super().inc
здесь, это именно связанный с текущим экземпляром метод, то есть полноценная "оригинальная версия"! Если бы вы вдруг решили вручную вызвать метод класса предка, то вам бы пришлось использовать его не связанную версию:
class DoubleCounter(Counter):
def inc(self):
Counter.inc(self) # явно обращаемся к методу класса предка
Counter.inc(self) # и передаём ссылку на экземпляр
Вызов super
вместо явного вызова предка хорош не только тем, что автоматически связывает методы. При смене предка (такое бывает) в описании класса super
учтёт изменения и вы получите доступ к поведению нового предка. Удобно!
super
работает не только с методами, но и с атрибутами классов:
>>> class A:
... x = 'A'
... class B(A):
... x = 'B'
... def super_x(self):
... return super().x
...
>>> B().x
'B'
>>> B().super_x()
'A'
Но важно помнить, что super
работает именно с классами. Вы не сможете получить доступ к атрибутам, которые добавляются в объект уже после того, как тот будет создан!
Функция
super
так названа в честь названия класса-предка: "superclass".
object
В прошлом мы не указывали предка в объявлениях классов, то есть писали так:
class Foo:
pass
В Python3 такая запись равнозначна записи class Foo(object):
. То есть, если класс-предок не указан, то таковым считается object
— самый базовый класс в Python. Сейчас, в эпоху повсеместного использования Python3, указывать или не указывать наследование от object
— дело вкуса.
А вот в Python2 class Foo:
и class Foo(object):
не были равнозначны! И это приводило к очень неприятным последствиям. Поэтому до сих пор можно встретить линтеры, которые жалуются на код без (object)
— вдруг вы захотите запустить код на старом добром втором пайтоне?
Линтер, который используем мы в Hexlet, как раз из таких — он будет педантично сообщать о каждом классе, который не унаследован явно от object
. Вам решать, будете ли вы указывать предка object
или отключите соответствующее предупреждение. Благо в Python3 оба варианта приемлемы и не противоречат друг другу!
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт