HTML Diff
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>