1 added
1 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Все классы, которые мы рассматривали до этого, создавались "с нуля". И до тех пор, пока описываемые классами сущности мало похожи друг на друга, создание абсолютно новых классов работает отлично. Но что делать, если мы хотим, чтобы пара классов содержала один и тот же метод - не одноименный, а именно копию?</p>
1
<p>Все классы, которые мы рассматривали до этого, создавались "с нуля". И до тех пор, пока описываемые классами сущности мало похожи друг на друга, создание абсолютно новых классов работает отлично. Но что делать, если мы хотим, чтобы пара классов содержала один и тот же метод - не одноименный, а именно копию?</p>
2
<p>Конечно же, мы можем при объявлении класса вместо объявления метода по месту поместить в атрибут ссылку на существующую функцию. И это даже сработает. Но когда таковых методов станет несколько, уследить за тем, что и куда копируется, станет очень сложно. К счастью, есть способ лучше.</p>
2
<p>Конечно же, мы можем при объявлении класса вместо объявления метода по месту поместить в атрибут ссылку на существующую функцию. И это даже сработает. Но когда таковых методов станет несколько, уследить за тем, что и куда копируется, станет очень сложно. К счастью, есть способ лучше.</p>
3
<p>Языки, реализующие инструментарий для объектно ориентированного программирования, включая использование классов, предоставляют и механизм<em>наследования</em>. Python - один из таких языков. Поэтому классы в Python можно<em>наследовать</em>.</p>
3
<p>Языки, реализующие инструментарий для объектно ориентированного программирования, включая использование классов, предоставляют и механизм<em>наследования</em>. Python - один из таких языков. Поэтому классы в Python можно<em>наследовать</em>.</p>
4
<p>Когда один класс становится наследником другого, то все атрибуты<em>класса-предка (надкласса, superclass)</em>становятся доступны<em>классу-потомку (подклассу, subclass)</em>- наследуются (достаются в наследство).</p>
4
<p>Когда один класс становится наследником другого, то все атрибуты<em>класса-предка (надкласса, superclass)</em>становятся доступны<em>классу-потомку (подклассу, subclass)</em>- наследуются (достаются в наследство).</p>
5
<h2>Что дает наследование</h2>
5
<h2>Что дает наследование</h2>
6
<p>Наследование позволяет выделить<em>общее для нескольких классов</em>поведение и вынести его в отдельную сущность. То есть наследование является средством<em>переиспользования кода (code reuse)</em>- использования существующего кода для решения новых задач.</p>
6
<p>Наследование позволяет выделить<em>общее для нескольких классов</em>поведение и вынести его в отдельную сущность. То есть наследование является средством<em>переиспользования кода (code reuse)</em>- использования существующего кода для решения новых задач.</p>
7
<p>Наследование позволяет получить новый класс,<em>немного отличающийся</em>от старого. При этом нам не нужно иметь доступ к коду исходного класса, а значит с помощью наследования мы можем<em>адаптировать</em>(использовать повторно) под наши задачи, в том числе и чужие классы.</p>
7
<p>Наследование позволяет получить новый класс,<em>немного отличающийся</em>от старого. При этом нам не нужно иметь доступ к коду исходного класса, а значит с помощью наследования мы можем<em>адаптировать</em>(использовать повторно) под наши задачи, в том числе и чужие классы.</p>
8
<p>Как обычно, рассмотрим пример:</p>
8
<p>Как обычно, рассмотрим пример:</p>
9
<p>Если мы выполним эти объявления классов и посмотрим на поведение экземпляра NonDecreasingCounter, то увидим, что он работает как Counter - имеет те же методы и атрибуты (правда, при вызове метода .dec новый счетчик не изменяет текущее значение):</p>
9
<p>Если мы выполним эти объявления классов и посмотрим на поведение экземпляра NonDecreasingCounter, то увидим, что он работает как Counter - имеет те же методы и атрибуты (правда, при вызове метода .dec новый счетчик не изменяет текущее значение):</p>
10
<p>В объявлении NonDecreasingCounter присутствует метод dec, а вот откуда взялись value и inc? Они были взяты от предка - класса Counter. Данный факт даже можно пронаблюдать:</p>
10
<p>В объявлении NonDecreasingCounter присутствует метод dec, а вот откуда взялись value и inc? Они были взяты от предка - класса Counter. Данный факт даже можно пронаблюдать:</p>
11
<p>Метод dec - метод класса NonDecreasingCounter, связанный с конкретным экземпляром NonDecreasingCounter. А вот inc - метод класса Counter, хоть и связанный с все тем же экземпляром класса-потомка.</p>
11
<p>Метод dec - метод класса NonDecreasingCounter, связанный с конкретным экземпляром NonDecreasingCounter. А вот inc - метод класса Counter, хоть и связанный с все тем же экземпляром класса-потомка.</p>
12
<p>Здесь вы можете увидеть сходство с взаимоотношениями между классом и его экземпляром: если экземпляр получает свой собственный атрибут, то этот атрибут заменяет атрибут класса. Точно так же объявления в классе-потомке заменяют собой атрибуты класса-предка, если имя используется то же самое - говорят,<em>переопределяют (override)</em>.</p>
12
<p>Здесь вы можете увидеть сходство с взаимоотношениями между классом и его экземпляром: если экземпляр получает свой собственный атрибут, то этот атрибут заменяет атрибут класса. Точно так же объявления в классе-потомке заменяют собой атрибуты класса-предка, если имя используется то же самое - говорят,<em>переопределяют (override)</em>.</p>
13
<p>И, как и в случае с объектом, который может использовать все содержимое класса и заменять только небольшую часть атрибутов (или добавлять новые.), так и потомок по умолчанию получает<em>все атрибуты предка</em>, часть из которых может изменить.</p>
13
<p>И, как и в случае с объектом, который может использовать все содержимое класса и заменять только небольшую часть атрибутов (или добавлять новые.), так и потомок по умолчанию получает<em>все атрибуты предка</em>, часть из которых может изменить.</p>
14
<h2>Все будет super()</h2>
14
<h2>Все будет super()</h2>
15
<p>Представим, что нас в целом устраивает класс Counter из предыдущего примера, но мы хотим при вызове inc увеличивать значение дважды. Мы могли бы заменить в потомке весь метод и прописать внутри нового метода self.value += 2. Но если бы позже что-то поменялось в исходном классе Counter, то эти изменения не коснулись бы нашего метода.</p>
15
<p>Представим, что нас в целом устраивает класс Counter из предыдущего примера, но мы хотим при вызове inc увеличивать значение дважды. Мы могли бы заменить в потомке весь метод и прописать внутри нового метода self.value += 2. Но если бы позже что-то поменялось в исходном классе Counter, то эти изменения не коснулись бы нашего метода.</p>
16
<p>Получается, что нам внутри метода потомка нужно получить доступ к методу предка. Методу с тем же именем. Если мы просто обратимся к self.inc, то получим ссылку на новый метод, ведь мы его<em>переопределили</em>.</p>
16
<p>Получается, что нам внутри метода потомка нужно получить доступ к методу предка. Методу с тем же именем. Если мы просто обратимся к self.inc, то получим ссылку на новый метод, ведь мы его<em>переопределили</em>.</p>
17
<p>Тут нам на помощь приходит специальная функция super. Функция super так названа в честь названия класса-предка: "superclass":</p>
17
<p>Тут нам на помощь приходит специальная функция super. Функция super так названа в честь названия класса-предка: "superclass":</p>
18
<p>Вызов super здесь заменяет обращение к self. При этом вы фактически обращаетесь к "памяти предков": получаете ссылку на атрибут предка. Более того, в данном случае, super().inc - это<em>связанный с текущим экземпляром</em>метод, то есть полноценная "оригинальная версия" из класса-предка. Если бы вы вдруг решили вручную вызвать метод класса предка, то вам бы пришлось использовать его не связанную версию:</p>
18
<p>Вызов super здесь заменяет обращение к self. При этом вы фактически обращаетесь к "памяти предков": получаете ссылку на атрибут предка. Более того, в данном случае, super().inc - это<em>связанный с текущим экземпляром</em>метод, то есть полноценная "оригинальная версия" из класса-предка. Если бы вы вдруг решили вручную вызвать метод класса предка, то вам бы пришлось использовать его не связанную версию:</p>
19
<p>Вызов super вместо явного вызова предка хорош не только тем, что автоматически связывает методы. При смене предка (такое бывает) в описании класса super учтет изменения, и вы получите доступ к поведению нового предка. Удобно.</p>
19
<p>Вызов super вместо явного вызова предка хорош не только тем, что автоматически связывает методы. При смене предка (такое бывает) в описании класса super учтет изменения, и вы получите доступ к поведению нового предка. Удобно.</p>
20
<p>super работает не только с методами, но и с атрибутами классов:</p>
20
<p>super работает не только с методами, но и с атрибутами классов:</p>
21
<p>Но важно помнить, что super работает именно с классами. Вы<em>не сможете</em>получить доступ к атрибутам, которые добавляются в объект уже после того, как тот будет создан.</p>
21
<p>Но важно помнить, что super работает именно с классами. Вы<em>не сможете</em>получить доступ к атрибутам, которые добавляются в объект уже после того, как тот будет создан.</p>
22
<h2>Вызов инициализатора суперкласса с super()</h2>
22
<h2>Вызов инициализатора суперкласса с super()</h2>
23
-
<p>При наследовании классов часто возникает необходимость не только добавить новые атрибуты или методы, но и расширить или изменить инициализацию объекта. В этом случае очень важно корректно вызвать конструктор суперкласса, чтобы вс�� атрибуты и состояние, которые должны быть наследованы, были правильно установлены.</p>
23
+
<p>При наследовании классов часто возникает необходимость не только добавить новые атрибуты или методы, но и расширить или изменить инициализацию объекта. В этом случае очень важно корректно вызвать конструктор суперкласса, чтобы все атрибуты и состояние, которые должны быть наследованы, были правильно установлены.</p>
24
<p>Использование super() в __init__ позволяет нам вызвать конструктор суперкласса, что гарантирует, что весь необходимый код инициализации будет выполнен:</p>
24
<p>Использование super() в __init__ позволяет нам вызвать конструктор суперкласса, что гарантирует, что весь необходимый код инициализации будет выполнен:</p>
25
<p>В этом примере метод __init__ в NonDecreasingCounter вызывает метод __init__ предка Counter с помощью super(). Это гарантирует, что атрибут value инициализируется как в Counter. Класс NonDecreasingCounter добавляет дополнительный атрибут non_decreasing и изменяет поведение метода dec, чтобы контролировать, может ли счетчик уменьшаться. Это демонстрирует, как можно расширить и настроить поведение классов при наследовании.</p>
25
<p>В этом примере метод __init__ в NonDecreasingCounter вызывает метод __init__ предка Counter с помощью super(). Это гарантирует, что атрибут value инициализируется как в Counter. Класс NonDecreasingCounter добавляет дополнительный атрибут non_decreasing и изменяет поведение метода dec, чтобы контролировать, может ли счетчик уменьшаться. Это демонстрирует, как можно расширить и настроить поведение классов при наследовании.</p>
26
<p>В контексте множественного наследования использование super() становится еще более важным, так как оно гарантирует, что все конструкторы суперклассов вызываются в правильном порядке. Это предотвращает проблемы с инициализацией и позволяет каждому классу в иерархии наследования вносить свой вклад в конечное состояние объекта.</p>
26
<p>В контексте множественного наследования использование super() становится еще более важным, так как оно гарантирует, что все конструкторы суперклассов вызываются в правильном порядке. Это предотвращает проблемы с инициализацией и позволяет каждому классу в иерархии наследования вносить свой вклад в конечное состояние объекта.</p>
27
<h2>Наследование и object</h2>
27
<h2>Наследование и object</h2>
28
<p>В прошлом мы не указывали предка в объявлениях классов, то есть писали так:</p>
28
<p>В прошлом мы не указывали предка в объявлениях классов, то есть писали так:</p>
29
<p>В Python3 такая запись равнозначна записи class Foo(object):. То есть, если класс-предок не указан, то таковым считается object - самый базовый класс в Python. Сейчас, в эпоху повсеместного использования Python3, указывать или не указывать наследование от object - дело вкуса.</p>
29
<p>В Python3 такая запись равнозначна записи class Foo(object):. То есть, если класс-предок не указан, то таковым считается object - самый базовый класс в Python. Сейчас, в эпоху повсеместного использования Python3, указывать или не указывать наследование от object - дело вкуса.</p>
30
<p>А вот в Python2 class Foo: и class Foo(object): не были равнозначны. И это приводило к очень неприятным последствиям. Поэтому до сих пор можно встретить линтеры, которые жалуются на код без (object) - вдруг вы захотите запустить код на старом добром втором Python?</p>
30
<p>А вот в Python2 class Foo: и class Foo(object): не были равнозначны. И это приводило к очень неприятным последствиям. Поэтому до сих пор можно встретить линтеры, которые жалуются на код без (object) - вдруг вы захотите запустить код на старом добром втором Python?</p>
31
<p>Вам решать, будете ли вы указывать предка object или отключите соответствующее предупреждение. Благо в Python3 оба варианта приемлемы и не противоречат друг другу.</p>
31
<p>Вам решать, будете ли вы указывать предка object или отключите соответствующее предупреждение. Благо в Python3 оба варианта приемлемы и не противоречат друг другу.</p>