Познаём Java. Вторая чашка: собираем классы в кучки. Пробуем апплеты
Как вы уже слышали, Java проектировалась с рассчётом на совместимость со всем, чем только можно. Такое ограничение вынудило разработчиков Java сделать её такой, чтобы максимально упростить развёртывание приложений, при этом обеспечив логическую стройность языка.
Как происходит загрузка классов?
Для того, чтобы найти класс по имени когда вы его вызываете, в Java существует стандартный загрузчик классов. Он оперирует понятием classpath. Список classpath — это набор путей, где следует искать файлы классов. Каждый classpath может указывать как на директорию, так и на так называемый jar-файл (зазипованная директория со скомпилированными .class’ами и разрешением .jar, типа «йА java-archive!»). По умолчанию в classpath входят файлы стандартных библиотек и директория, из которой вы вызвали саму Java. Именно таким образом был найден класс HelloWorld — java нашла файл HelloWorld.class и запустила в нём метод main. Собственно, так и работают большинство программ, даже самых сложных. Всё начинается с одного main’а…
Пакеты классов
Пакет (package) представляют собой набор классов, объединённых по смыслу. Пакеты обычно вкладываются друг в друга, образуя собо иерархию, дающую понять, что зачем. На файловой системе такая иерархия выглядит в виде вложенных друг в друга директорий с исходниками. Так, исходники пакета a лежат в папке a, исходники пакета a.b — в папке a/b и так далее. Типичный путь к пакету выглядит примерно так:
org.apache.commons.collectons . Видите, сразу ясно зачем он нужен. Чтобы использовать какой-то класс в коде другого класса, вы должны импортировать его, написав до объявления класса строку
import путь.к.классу.ИмяКласса;
Кроме того, если вы используете классы одного пакета часто, вы можете импортировать весь пакет:
import путь.к.классу.*;
Это относится ко всем пакетам, кроме java.lang — он импортирован по умолчанию, именно из него были в прошлом примере взяты классы System и String. На самом деле они лежат в некоем jar’е, в каталоге java/lang.
Что ж, теперь вы знаете как работает загрузчик классов. В реальных проектов количество classpath измеряется десятками, а то и сотнями.
Организация кода
Если вы пишете свои первые маленькие примерчики и вам лень создавать иерархию классов — пусть, это ваше право. Но помните, в серьёзном проекте вы всегда должны будете разложить свои классы по пакетам. Обычно корневые пакеты создаются такими, чтобы ясно давать понять кто автор кода, и к чему он относится.
Например:
ru.vasiapupkin.photomaker выбирается корневым пакетом
ru.vasiapupkin.photomaker.core сюда мы пишем классы отвечающие за логику
ru.vasiapupkin.photomaker.visual сюда, допустим, все наши окошки приложения
и так далее.
Чтобы создать класс
ru.vasiapupkin.photomaker.Starter
вы должны:
создать файл Starter.java в папке ru/vasiapupkin/photomaker/
прописать в нём первой строчкой (точнее говоря, до импортов)
package ru.vasiapupkin.photomaker;
Коллижн
«А что будет, если у нас будет два класса с одним именем?», — спросите вы. «Смотрите», — отвечу я.
Допустим вы решили что вы умнее джавы и создали свой класс строки — String. Но вот проблема, у нас же уже есть такой!
Значит, вам придётся положить свой класс в пакет, скажем ru.vp.stuff и обращаться к нему так: ru.vp.stuff.String.
Именно поэтому не рекомендуется класть классы прямо в корень classpath — таким образом вы роете себе дорогу к несовместимости, ведь Java требует, чтобы каждый класс определялся однозначно. Именно поэтому нельзя написать так:
import ru.vp.SuperClass;
import ru.mashka.SuperClass;
За это вас накажет компилятор, потому что он не будет знать, какой из них использовать.
Мораль: правильно выбирайте и имя класс и имя пакета.
Погоняем?
Давайте улучшим первое приложение. Эх, классика интернета… Создадим апплет.
Эх, может быть апплетами.
import java.applet.Applet ;
import java.awt.Graphics ;
public class HelloWorld extends Applet
public void paint( Graphics g) g.drawString( «Hello World» ,15,15);
>
>
* This source code was highlighted with Source Code Highlighter .
Так, что у нас тут? Импортировано 2 класса, один из них — стандартный пустой апплет, который мы будем расширять. Второй — Graphics. Graphics — это понятие из библиотеки AWT. Кстати, небольшой экскурс. AWT (Abstract Window Toolkit) входил ещё в первую Java и был предназначен для многих задач, связанных в основном с отображением.
Так вот, объект типа Graphics позволяет нам рисовать на себе всякую муру типа строк, линий, кружочков и прочего. В данном примере мы написали строчку с отступом.
Метод paint здесь написан не от балды — он перекрывает аналогичный метод класса Applet, и когда java будет перерисовывать этот конкретный апплет, она вызовет этот метод.
Посмотреть на наш апплет достаточно просто — пишем небольшой HTML:
< body >
< applet code ="HelloWorld.class" codebase ="file:///home/devgru/" width ="150" height ="30" > applet >
body >* This source code was highlighted with Source Code Highlighter .
… а может приложением.
Давайте попробуем сделать HelloWorld в standalone-приложении.
import java.awt.* ;
import javax.swing.* ;
public class HelloWorld extends JFrame
public static void main( String [] args) new HelloWorld();
>
add( new JLabel( «Hello world» ));
setSize(200,200);
setVisible( true );
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
>
>
* This source code was highlighted with Source Code Highlighter .
Здесь мы полностью импортируем основные классы пакетов Swing и AWT. Swing — более поздняя чем AWT библиотека, сейчас именно она обеспечивает отображение основной части графического интерфейса для Java-приложений.
Итак, в main мы просто создаём экземпляр класса HelloWorld.
Сейчас наш класс наследуется от класса JFrame. Это класс из Swing, он представляет собой окно, которое отображается пока не будет закрыто.
Блок <. >— это «общий конструктор». Он добавляется к любому конструктору нашего класса. Так как у нас нет ни одного — он добавляется к пустому конструктору без параметров, который создаётся на лету, если у класса нет ни одного.
Мы добавляем на окно новый объект типа JLabel (т.е. надпись), затем устанавливаем окну размеры и отображаем его. Последняя строчка нужна, чтобы выполнение приложения закончилось, когда будет закрыто окно. Таким образом, вы можете быть уверены что после закрытия окна у вас в памяти не останется висеть ваше приложение.
Запускать его нужно точно так же как и прошлое: пишем, компилируем, запускаем.
А может и сервлетами? Наверное, потом.
В этих двух статьях я постарался дать вам начальное представление о возможностях Java в общем. За рамками сегодняшней статьи (наверное, будут в завтрашней) остались сервлеты и прочие радости серверной части типа JSP-страниц, а также МИДлеты — приложения для мобилок. Я бы мог рассмотреть и то и то, но хотел бы знать, чего больше хотят читатели. Кроме того, возможно, нужно рассказать о самых основах языка. Примерно на том же уровне подробности, что и начало этой статьи. Напишите в комментариях, какую статью вы хотели бы видеть в следующий раз:
— классы и интерфейсы: ООП в джаве;
— буквы-цифры-строчки: работа с базовыми типами;
— создание оконных приложений с помощью Swing;
— от мала до велика: сервлеты, мидлеты и 2 слова о портлетах.
Когда отпишетесь — станет ясно, куда копать дальше. Всем спасибо за внимание.
Ссылка для тех, кому лень ждать завтра: основы языка (eng.).
Есть два класса a и b которые не связаны между собой наследованием и лежат в одном пакете
Как правило, в Java классы объединяются в пакеты. Пакеты позволяют организовать классы логически в наборы. По умолчанию java уже имеет ряд встроенных пакетов, например, java.lang , java.util , java.io и т.д. Кроме того, пакеты могут иметь вложенные пакеты.
Организация классов в виде пакетов позволяет избежать конфликта имен между классами. Ведь нередки ситуации, когда разработчики называют свои классы одинаковыми именами. Принадлежность к пакету позволяет гарантировать однозначность имен.
Чтобы указать, что класс принадлежит определенному пакету, надо использовать директиву package , после которой указывается имя пакета:
package название_пакета;
Как правило, названия пакетов соответствуют физической структуре проекта, то есть организации каталогов, в которых находятся файлы с исходным кодом. А путь к файлам внутри проекта соответствует названию пакета этих файлов. Например, если классы принадлежат пакету mypack, то эти классы помещаются в проекте в папку mypack.
Классы необязательно определять в пакеты. Если для класса пакет не определен, то считается, что данный класс находится в пакете по умолчанию, который не имеет имени.
Например, создадим в папке для исходных файлов каталог study . В нем создадим файл Program.java со следующим кодом:
package study; public class Program < public static void main(String[] args) < Person kate = new Person("Kate", 32); kate.displayInfo(); >> class Person < String name; int age; Person(String name, int age)< this.name = name; this.age = age; >void displayInfo() < System.out.printf("Name: %s \t Age: %d \n", name, age); >>
Директива package study в начале файла указывает, что классы Program и Person, которые здесь определены, принадлежат пакету study.
Когда мы работаем в среде разработки, например, в Netbeans, то IDE берет на себя все вопросы компиляции пакетов и входящих в них файлов. Соответственно нам достаточно нажать на кнопку, и все будет готово. Однако если мы компилируем программу в командной строке, то мы можем столкнуться с некоторыми трудностями. Поэтому рассмотрим этот аспект.
Для компиляции программы вначале в командной строке/терминале с помощью команды cd перейдем к папке, где находится каталог study.
cd C:\java
Например, в моем случае это каталог C:\java (то есть файл с исходным кодом расположен по пути C:\java\study\Program.java).
Для компиляции выполним команду
javac study\Program.java
После этого в папке study появятся скомпилированные файлы Program.class и Person.class. Для запуска программы выполним команду:
java study.Program
Импорт пакетов и классов
Если нам надо использовать классы из других пакетов, то нам надо подключить эти пакеты и классы. Исключение составляют классы из пакета java.lang (например, String ), которые подключаются в программу автоматически.
Например, знакомый по прошлым темам класс Scanner находится в пакете java.util , поэтому мы можем получить к нему доступ следующим способом:
java.util.Scanner in = new java.util.Scanner(System.in);
То есть мы указываем полный путь к файлу в пакете при создании его объекта. Однако такое нагромождение имен пакетов не всегда удобно, и в качестве альтернативы мы можем импортировать пакеты и классы в проект с помощью директивы import , которая указывается после директивы package:
package study; import java.util.Scanner; // импорт класса Scanner public class Program < public static void main(String[] args) < Scanner in = new Scanner(System.in); >>
Директива import указывается в самом начале кода, после чего идет имя подключаемого класса (в данном случае класса Scanner).
В примере выше мы подключили только один класс, однако пакет java.util содержит еще множество классов. И чтобы не подключать по отдельности каждый класс, мы можем сразу подключить весь пакет:
import java.util.*; // импорт всех классов из пакета java.util
Теперь мы можем использовать любой класс из пакета java.util.
Возможна ситуация, когда мы используем два класса с одним и тем же названием из двух разных пакетов, например, класс Date имеется и в пакете java.util , и в пакете java.sql . И если нам надо одновременно использовать два этих класса, то необходимо указывать полный путь к этим классам в пакете:
java.util.Date utilDate = new java.util.Date(); java.sql.Date sqlDate = new java.sql.Date();
Статический импорт
В java есть также особая форма импорта — статический импорт. Для этого вместе с директивой import используется модификатор static :
package study; import static java.lang.System.*; import static java.lang.Math.*; public class Program < public static void main(String[] args) < double result = sqrt(20); out.println(result); >>
Здесь происходит статический импорт классов System и Math. Эти классы имеют статические методы. Благодаря операции статического импорта мы можем использовать эти методы без названия класса. Например, писать не Math.sqrt(20) , а sqrt(20) , так как функция sqrt() , которая возвращает квадратный корень числа, является статической. (Позже мы рассмотрим статические члены класса).
То же самое в отношении класса System: в нем определен статический объект out , поэтому мы можем его использовать без указания класса.
Есть два класса a и b которые не связаны между собой наследованием и лежат в одном пакете
Задачи получилось решить интуитивно + ide дает подсказки. После этого урока рекомендую прочитать https://javarush.com/groups/posts/modifikator-static-java — здесь не все понимаешь с первого раза (особенно концовку), но текст легче воспринимается
Elizaveta Trokhina Уровень 13 Expert
11 ноября 2023
лекция просто капец, ничего не понятно. что-то говорится, примеры явно не в тему. задачи тоже как бы не особо раскрывают тему( пойду посмотрю допматериалы
Novikova Natalia Уровень 17
7 ноября 2023
«Статические методы можно вызывать откуда угодно — из любого места программы. А значит, их можно вызывать и из статических методов, и из обычных. Никаких ограничений тут нет.» — а по модификатору доступа к статическому методу?
Irina Michurina Уровень 16
29 октября 2023
Здесь крайне непонятно объясняется про статические и обычные переменные и методы — слишком много ненужных слов и непонятных картинок без конкретных примеров. Лучше почитать в других источниках (в тех же статьях на этом сайте более понятно объясняется).
Defer Уровень 15
19 октября 2023
Optimus Уровень 17
17 октября 2023
Не работает кнопка «button_unlock_java_syntax_pro_lesson_4_12» Для перехода на следующую лекцию, нет ссылки у нее Удалось зайти в лекцию 4, через задачи, вызвав недоступную задачу из этой лекции. Там кнопка работает.
kivkaOne Уровень 12
8 октября 2023
если кто-то хочет пройти этот путь вместе — пишите.. тг: @kivkaOne
Владимир Уровень 16
27 сентября 2023
/* Комментарий удален */
Dmitry Shaburov Уровень 32
15 сентября 2023
Navotno Уровень 15
12 сентября 2023
Я сначала посмотрел лекцию Наиля Алишева, всё понял, потом прочитал тут и опять запутался.
Сообщество
JavaRush — это интерактивный онлайн-курс по изучению Java-программирования c нуля. Он содержит 1200 практических задач с проверкой решения в один клик, необходимый минимум теории по основам Java и мотивирующие фишки, которые помогут пройти курс до конца: игры, опросы, интересные проекты и статьи об эффективном обучении и карьере Java‑девелопера.
Подписывайтесь
Язык интерфейса
«Программистами не рождаются» © 2023 JavaRush
Скачивайте наши приложения
«Программистами не рождаются» © 2023 JavaRush
Этот веб-сайт использует данные cookie, чтобы настроить персонально под вас работу сервиса. Используя веб-сайт, вы даете согласие на применение данных cookie. Больше подробностей — в нашем Пользовательском соглашении.
Урок 6. Принципы ООП. Классы, объекты, поля и методы. Уровни доступа.
Поговорим про основные принципы объектно-ориентированного программирования: абстракцию, инкапсуляцию, наследование и полиморфизм. Научимся создавать классы и объекты классов в Python. Рассмотрим, чем отличаются понятия поля, свойства, методы и атрибуты класса. Изучим особенности организации уровней доступа к атрибутам: Public, Protected и Private.

Курс «Программирование на Python»
Урок 6.
Принципы ООП. Классы, объекты, поля и методы. Уровни доступа.
Поговорим про основные принципы объектно-ориентированного программирования: абстракцию, инкапсуляцию, наследование и полиморфизм. Научимся создавать классы и объекты классов в Python. Рассмотрим, чем отличаются понятия поля, свойства, методы и атрибуты класса. Изучим особенности организации уровней доступа к атрибутам: Public, Protected и Private.
Предыдущий урок
Урок 5. Модули и пакеты в Python. Импорт. Виртуальная среда venv.
Решения к задачам по курсу «Программирование на Python»
ТЕОРЕТИЧЕСКИЙ БЛОК
В КОНЦЕ УРОКА ЕСТЬ ВИДЕО

Что такое ООП?
- Процедурное программирование
- Объектно-ориентированное программирование (оно же ООП)
По сути любая программа представляет собой совокупность данных и операций по их обработке. Но что важнее, сами данные или операции над ними? В языках, в основе работы которых лежит принцип процедурного программирования ( Basic , C , Pascal , Go ), главным является код для обработки данных. При этом сами данные имеют второстепенное значение.

Процедурное программирование. Работа с данными.
В чем суть процедурного подхода? Процедурное программирование – это написание функций и их последовательный вызов в некоторой главной( main ) функции.
Для каких проектов подходит процедурное программирование? Идеальные условия для применения данного подхода — простые программы, где весь функционал можно реализовать несколькими десятками процедур/функций. Функции аккуратно вложены друг в друга и легко взаимодействуют посредством передачи данных из одной функции в другую.
Однако проблема в том, что когда мы выходим за пределы этих идеальных условий, выплывают наружу многие недостатки данного подхода:
- В больших проектах приходится создавать огромное количество процедур и функций. В свою очередь, это неизбежно ведет к возникновению множества ошибок и снижает читаемость кода программы.
- Все данные внутри процедуры видны только локально, а значит их нельзя использовать в другом месте. Как следствие, код наполняется дубликатами.
- Высокий порог вхождения — по статистике начинающим данный поход дается сложнее, чем ООП.
Долгое время процедурный подход был довольно популярным и считался самым прогрессивным. Однако объемы кода программ росли, и ему на смену пришло объектно-ориентированное программирование. Вот как определяет данный подход Википедия:
Объектно-ориентированное программирование (ООП) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определённого класса, а классы образуют иерархию наследования.
В основе ООП лежит простая и элегантная идея, в соответствии с которой главное в программе — это данные. Именно они определяют, какие методы будут использоваться для их обработки. Т. е. данные первичны, код для обработки этих данных — вторичен.

Объектно-ориентированное программирование. Работа с данными.
- Программа разбивается на объекты. Каждый объект отвечает за собственные данные и их обработку. Как результат — код становится проще и читабельней.
- Уменьшается дупликация кода. Нужен новый объект, содержимое которого на 90% повторяет уже существующий? Давайте создадим новый класс и унаследуем эти 90% функционала от родительского класса!
- Упрощается и ускоряется процесс написания программ. Можно сначала создать высокоуровневую структуру классов и базовый функционал, а уже потом перейти к их подробной реализации.
- В процедурном подходе основой программы является функция. Функции вызывают друг друга и при необходимости передают данные. В программе функции живут отдельно, данные — отдельно.
- Основной недостаток процедурного подхода — сложность создания и поддержки больших программ. Наличие сотен функций в таких проектах очень часто приводит к ошибкам и спагетти-коду.
- В основе объектно-ориентированного программирования лежит понятие объекта. Объект совмещает в себе и функции и данные.
- Основное преимущество ООП перед процедурным программированием — изоляция кода на уровне классов, что позволяет писать более простой и лаконичный код.

Объекты и классы в ООП
Мир, в котором мы живем, состоит из объектов. Это деревья, солнце, горы, дома, машины, бытовая техника. Каждый из этих объектов имеет свой набор характеристик и предназначение. Несложно догадаться, что именно объектная картина реального мира легла в основу ООП. Разберем несколько ключевых понятий, основываясь на Википедии:
Класс — в объектно-ориентированном программировании, представляет собой шаблон для создания объектов, обеспечивающий начальные значения состояний: инициализация полей-переменных и реализация поведения функций или методов.
Объект — некоторая сущность в цифровом пространстве, обладающая определённым состоянием и поведением, имеющая определенные свойства (поля) и операции над ними (методы). Как правило, при рассмотрении объектов выделяется то, что объекты принадлежат одному или нескольким классам, которые определяют поведение (являются моделью) объекта. Термины «экземпляр класса» и «объект» взаимозаменяемы.
- Класс описывает множество объектов, имеющих общую структуру и обладающих одинаковым поведением. Класс — это шаблон кода, по которому создаются объекты. Т. е. сам по себе класс ничего не делает, но с его помощью можно создать объект и уже его использовать в работе.
- Данные внутри класса делятся на свойства и методы. Свойства класса (они же поля) — это характеристики объекта класса.
- Методы класса — это функции, с помощью которых можно оперировать данными класса.
- Объект — это конкретный представитель класса.
- Объект класса и экземпляр класса — это одно и то же.
Грубо говоря
Класс = Свойства + Методы
- Цвет
- Объем двигателя
- Мощность
- Тип коробки передач
- Ехать
- Остановиться
- Заправиться
- Поставить на сигнализацию
- Включить дворники
Что общего будет в объектах? Все объекты создаются по одному шаблону, то есть на выходе обязательно будут машины, никаких велосипедов и мотоциклов. Они будут выкрашены в какой-то цвет, ехать они будут за счет наличия в них двигателя, скорость будет регулироваться с помощью коробки передач. Также объекты данного класса будут обладать одинаковыми методами — все машины этого класса будут ездить, периодически им будет нужна заправка, а от угона они будут защищены установкой сигнализации.

ООП. Класс Автомобиль.
Но в чем разница? Значения свойств будут различаться. Одна машина красная, другая — зеленая. У одной объем двигателя 1968 см3 и коробка-робот, а у другой — 1395 см3 и ездить владельцу придется на механике.
Вывод: Объекты класса на выходе похожие и одновременно разные. Различаются, как правило, свойства. Методы остаются одинаковыми.
- Свойства: Цвет=»Белый», Объем двигателя=»1984 см3″, Мощность=»180 л.с.», Тип коробки передач=»Робот»
- Методы: Ехать, Остановиться, Заправиться, Поставить на сигнализацию, Включить дворники

Принципы ООП: абстракция, инкапсуляция, наследование, полиморфизм
Как мы уже сказали, на текущий момент ООП является самой востребованной и распространенной парадигмой программирования. Концепция ООП строится на основе 4 принципов, которые мы предлагаем вам кратко рассмотреть.
Принцип 1. Абстракция
Дадим определение:
Абстракция — принцип ООП, согласно которому объект характеризуется свойствами, которые отличают его от всех остальных объектов и при этом четко определяют его концептуальные границы.
- Выделить главные и наиболее значимые свойства предмета.
- Отбросить второстепенные характеристики.
Зачем нужна абстракция? Если мыслить масштабно — то она позволяет бороться со сложностью реального мира. Мы отбрасываем все лишнее, чтобы оно нам не мешало, и концентрируемся только на важных чертах объекта.

ООП. Абстракция.
Принцип 2. Инкапсуляция
Абстракция утверждает следующее: «Объект может быть рассмотрен с общей точки зрения». А инкапсуляция от себя добавляет: «И это единственная точка зрения, с которой вы вообще можете рассмотреть этот объект.». А если вы внимательно посмотрите на название, то увидите в нем слово «капсула». В этой самой «капсуле» спрятаны данные, которые мы хотим защитить от изменений извне.
Инкапсуляция — принцип ООП, согласно которому сложность реализации программного компонента должна быть спрятана за его интерфейсом.
- Отсутствует доступ к внутреннему устройству программного компонента.
- Взаимодействие компонента с внешним миром осуществляется посредством интерфейса, который включает публичные методы и поля.
- Инкапсуляция упрощает процесс разработки, т. к. позволяет нам не вникать в тонкости реализации того или иного объекта.
- Повышается надежность программ за счет того, что при внесении изменений в один из компонентов, остальные части программы остаются неизменными.
- Становится более легким обмен компонентами между программами.

ООП. Инкапсуляция.
Принцип 3. Наследование
Наследование используется в случае, если одни объекты аналогичны другим за исключением нескольких различий. Дадим определение:
Наследование — способ создания нового класса на основе уже существующего, при котором класс-потомок заимствует свойства и методы родительского класса и также добавляет собственные.
- Класс-потомок = Свойства и методы родителя + Собственные свойства и методы.
- Класс-потомок автоматически наследует от родительского класса все поля и методы.
- Класс-потомок может дополняться новыми свойствами.
- Класс-потомок может дополняться новыми методами, а также заменять(переопределять) унаследованные методы. Переопределить родительский метод — это как? Это значит, внутри класса потомка есть метод, который совпадает по названию с методом родительского класса, но функционал у него новый — соответствующий потребностям класса-потомка.
- Дает возможность использовать код повторно. Классы-потомки берут общий функционал у родительского класса.
- Способствует быстрой разработке нового ПО на основе уже существующих открытых классов.
- Наследование позволяет делать процесс написания кода более простым.
СВОЙСТВА
1) Тип фундамента
2) Материал крыши
3) Количество окон
4) Количество дверей
МЕТОДЫ
1) Построить
2) Отремонтировать
3) Заселить
4) Снести
А если мы захотим создать объект Частный дом? Данный объект по-прежнему будет являться домом, а значит будет обладать свойствами и методами класса Дом. Например, в нем так же есть окна и двери, и такой дом по-прежнему можно построить или отремонтировать. Однако при этом у него также будут собственные свойства и методы, ведь именно они отличают новый класс от его родителя. Новый класс Частный дом может выглядеть следующим образом:
СВОЙСТВА
1) Тип фундамента (УНАСЛЕДОВАНО)
2) Материал крыши (УНАСЛЕДОВАНО)
3) Количество окон (УНАСЛЕДОВАНО)
4) Количество дверей (УНАСЛЕДОВАНО)
5) Количество комнат
6) Тип отопления
7) Наличие огорода
МЕТОДЫ
1) Построить (УНАСЛЕДОВАНО)
2) Отремонтировать (УНАСЛЕДОВАНО)
3) Заселить (УНАСЛЕДОВАНО)
4) Снести (УНАСЛЕДОВАНО)
5) Изменить фасад
6) Утеплить
7) Сделать пристройку
С такой же легкостью мы можем создать еще один класс-потомок — Многоэтажный дом. Его свойства и методы могут выглядеть так:
СВОЙСТВА
1) Тип фундамента (УНАСЛЕДОВАНО)
2) Материал крыши (УНАСЛЕДОВАНО)
3) Количество окон (УНАСЛЕДОВАНО)
4) Количество дверей (УНАСЛЕДОВАНО)
5) Количество квартир
6) Количество подъездов
7) Наличие коммерческой недвижимости
МЕТОДЫ
1) Построить (УНАСЛЕДОВАНО)
2) Отремонтировать (УНАСЛЕДОВАНО)
3) Заселить (УНАСЛЕДОВАНО)
4) Снести (УНАСЛЕДОВАНО)
5) Выбрать управляющую компанию
6) Организовать собрание жильцов
7) Нанять дворника
Т. е. если подытожить, наследование позволяет нам использовать функционал уже существующих классов для создания новых. Просто представьте, что будет, если возможность наследования исчезнет. В этом случае пришлось бы копировать свойства и методы класса-родителя в класс-потомок. А если наследников 2? 5? 20? Придется копировать 20 раз. А что, если с течением времени вам потребуется внести изменение в код базового класса? Будете менять код во всех 20ти потомках? А что, если изменений тоже не одно, а например, 100? Думаю, что теперь вы убеждены, что наследование не зря является базовым принципом объектно-ориентированного программирования.

Принцип 4. Полиморфизм
В данном случае глубоко вдаваться в подробности не будем(так как по-хорошему это тема для целой статьи), но суть данного принципа постараемся передать.
Полиморфизм — это поддержка нескольких реализаций на основе общего интерфейса.
Другими словами, полиморфизм позволяет перегружать одноименные методы родительского класса в классах-потомках.
Также для понимания работы этого принципа важным является понятие абстрактного метода:
Абстрактный метод (он же виртуальный метод) — это метод класса, реализация для которого отсутствует.
- В родительском классе(в нашем случае — класс Дом) создают пустой метод(например, метод Построить() ) и делают его абстрактным.
- В классах-потомках создают одноименные методы, но уже с соответствующей реализацией. И это логично, ведь например, процесс постройки Частного и Многоквартирного дома отличается кардинально. К примеру, для строительства Многоквартирного дома необходимо задействовать башенный кран, а Частный дом можно построить и собственными силами. При этом данный процесс все равно остается процессом строительства.
- В итоге получаем метод с одним и тем же именем, который встречается во всех классах. В родительском — имеем только интерфейс, реализация отсутствует. В классах-потомках — имеем и интерфейс и реализацию. Причем в отличие от родительского класса реализация в потомках уже становится обязательной.
- Теперь мы можем увидеть полиморфизм во всей его красе. Даже не зная, с объектом какого из классов-потомков мы работаем, нам достаточно просто вызвать метод Построить(). А уже в момент исполнения программы, когда класс объекта станет известен, будет вызвана необходимая реализация метода Построить().
Как итог — за одинаковым названием могут скрываться методы с совершенно разным функционалом, который в каждом конкретном случае соответствует нуждам класса, к которому он относится.


Классы и объекты в Python
Теперь давайте посмотрим, как реализуется ООП в рамках языка программирования Python. Синтаксис для создания класса выглядит следующим образом:
class :
А вот так компактно смотрится пример объявления класса с минимально возможным функционалом:
class Car: pass
Как мы видим, для задания класса используется инструкция class , далее следует имя класса Car и двоеточие. После них идет тело класса, которое в нашем случае представлено оператором pass . Данный оператор сам по себе ничего не делает — фактически это просто заглушка.
Чтобы создать объект класса, нужно воспользоваться следующим синтаксисом:
И в качестве примера создадим объект класса Car :
car_object = Car()

Атрибуты класса в Python
Давайте договоримся, что атрибутом класса/объекта мы будем называть любой элемент класса/объекта (переменную, метод, подкласс), на который мы можем сослаться через символ точки. Т. е. вот так: MyClass. или my_object. .
- Встроенные(служебные) атрибуты
- Пользовательские атрибуты

1. Встроенные атрибуты
Называть данную группу атрибутов встроенными — это своего рода условность, и сейчас мы объясним почему. Суть в том, что на самом деле все классы в Python (начиная с 3-й версии) имеют один общий родительский класс — object . Это значит, что когда вы создаете новый класс, вы неявно наследуете его от класса object , и потому свежесозданный класс наследует его атрибуты. Именно их мы и называем встроенными(служебными). Вот некоторые из них(заметьте, что в списке есть как поля, так и методы):
Атрибут;Назначение;Тип
__new__(cls[, . ]);Конструктор. Создает экземпляр(объект) класса. Сам класс передается в качестве аргумента.;Функция __init__(self[, . ]);Инициализатор. Принимает свежесозданный объект класса из конструктора.;Функция __del__(self);Деструктор. Вызывается при удалении объекта сборщиком мусора;Функция __str__(self);Возвращает строковое представление объекта.;Функция __hash__(self);Возвращает хэш-сумму объекта.;Функция __setattr__(self, attr, val);Создает новый атрибут для объекта класса с именем attr и значением val;Функция __doc__;Документация класса.;Строка (тип str) __dict__;Словарь, в котором хранится пространство имен класса;Словарь (тип dict)
Это важно
В теории ООП конструктор класса — это специальный блок инструкций, который вызывается при создании объекта. При работе с питоном может возникнуть мнение, что метод __init__(self) — это и есть конструктор, но это не совсем так. На самом деле, при создании объекта в Python вызывается метод __new__ (cls, *args, **kwargs) и именно он является конструктором класса.
Также обратите внимание, что __new__() — это метод класса, поэтому его первый параметр cls — ссылка на текущий класс. В свою очередь, метод __init__() является так называемым инициализатором класса. Именно этот метод первый принимает созданный конструктором объект. Как вы уже, наверное, не раз замечали, метод __init__() часто переопределяется внутри класса самим программистом. Это позволяет со всем удобством задавать параметры будущего объекта при его создании.
2. Пользовательские атрибуты
Это атрибуты, которые непосредственно составляют основной функционал класса. Если служебные атрибуты наследуются от базового класса object , то пользовательские — пишутся программистом во время реализации начинки класса и дальнейшей работы с ним.
Список атрибутов класса / объекта можно получить с помощью команды dir() . Если взять самый простой класс:
class Phone: pass
То мы получим вот такой список атрибутов:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
Как видим, в нем есть только встроенные атрибуты, которые наш класс по-умолчанию унаследовал от базового класса object . А теперь добавим ему функционала:
class Phone: color = 'Grey' def turn_on(self): pass def call(self): pass
И теперь посмотрим, как изменился список атрибутов класса:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'color', 'call', 'turn_on']
Несложно заметить, что в конце списка добавились три новых пользовательских атрибута: переменная color и методы turn_on() и call() .
- Атрибутами называем совокупность полей и методов класса / объекта.
- Атрибуты делятся на встроенные и пользовательские.
- Все классы в Python имеют общий родительский класс — он называется object .
- Класс object предоставляет всем своим потомкам набор служебных атрибутов (как переменных (например, __dict__ и __doc__ ), так и методов (например, __str__ ) ).
- Многие из служебных атрибутов можно переопределить внутри своего класса.
- Поля и методы, которые описываются программистом в теле класса, являются пользовательскими и добавляются в общий список атрибутов наряду со встроенными атрибутами.

Поля (свойства) класса в Python
- Статические поля
- Динамические поля

1. Статические поля (они же переменные или свойства класса)
Это переменные, которые объявляются внутри тела класса и создаются тогда, когда создается класс. Создали класс — создалась переменная:
class Phone: # Статические поля (переменные класса) default_color = 'Grey' default_model = ‘C385’ def turn_on(self): pass def call(self): pass
Вот так выглядит список атрибутов класса после его создания:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'call', 'default_color', 'default_model', 'turn_on']
Кроме того, у нас есть возможность получить или изменить такое свойство, просто обратившись к самому классу по его имени(экземпляр класса при этом создавать не нужно).
Python — Интерактивный режим
>>> Phone.default_color 'Grey' # Изменяем цвет телефона по умолчанию с серого на черный >>> Phone.default_color = 'Black' >>> Phone.default_color 'Black'
2. Динамические поля (переменные или свойства экземпляра класса)
Это переменные, которые создаются на уровне экземпляра класса. Нет экземпляра — нет его переменных. Для создания динамического свойства необходимо обратиться к self внутри метода:
class Phone: # Статические поля (переменные класса) default_color = 'Grey' default_model = 'C385' def __init__(self, color, model): # Динамические поля (переменные объекта) self.color = color self.model = model
Python — Интерактивный режим
# Создадим экземпляр класса Phone - телефон красного цвета модели 'I495’ >>> my_phone_red = Phone('Red', 'I495') # Полный список атрибутов созданного экземпляра: >>> dir(my_phone_red) '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'color', 'default_color', 'default_model', 'model'] # Прочитаем статические поля объекта >>> my_phone_red.default_color 'Grey' >>> my_phone_red.default_model 'C385' # Прочитаем динамические поля объекта >>> my_phone_red.color 'Red' >>> my_phone_red.model 'I495' # Создадим еще один экземпляр класса Phone - такой же телефон, но другого цвета >>> my_phone_blue = Phone('Blue', 'I495') # Прочитаем динамические поля объекта >>> my_phone_blue.color 'Blue' >>> my_phone_blue.model 'I495'
Что такое self в Python?
Служебное слово self — это ссылка на текущий экземпляр класса. Как правило, эта ссылка передается в качестве первого параметра метода Python:
class Apple:
____# Создаем объект с общим количеством яблок 12
____def __init__(self):
________self.whole_amount = 12
____# Съедаем часть яблок для текущего объекта
____def eat(self, number):
________self.whole_amount -= number
Стоит обратить внимание, что на самом деле слово self не является зарезервированным. Просто существует некоторое соглашение, по которому первый параметр метода именуется self и передает ссылку на текущий объект, для которого этот метода был вызван. Хотите назвать первый параметр метода по-другому — пожалуйста.
В других языках программирования(например, Java или C++) аналогом этого ключа является служебное слово this .
Обратите внимание, что объект класса сочетает в себе как статические атрибуты(уровня класса), так и динамические(собственные — уровня объекта).
- Для создания статической переменной достаточно объявления класса, причем данная переменная создается непосредственно в теле класса.
- Динамические переменные создаются только в рамках работы c экземпляром класса.
- Чтоб создать переменную экземпляра, необходимо воспользоваться конструкцией self. внутри одного из методов.
- Экземпляр класса сочетает в себе совокупность как статических (уровня класса), так и динамических (уровня самого экземпляра) полей.
- Значения динамических переменных для разных объектов класса могут (и чаще всего так и делают) различаться.

Методы (функции) класса в Python
- Методы экземпляра класса (они же обычные методы)
- Статические методы
- Методы класса

1. Методы экземпляра класса (Обычные методы)
Это группа методов, которые становятся доступны только после создания экземпляра класса, то есть чтобы вызвать такой метод, надо обратиться к экземпляру. Как следствие — первым параметром такого метода является слово self . И как мы уже обсудили выше, с помощью данного параметра в метод передается ссылка на объект класса, для которого он был вызван. Теперь пример:
class Phone: def __init__(self, color, model): self.color = color self.model = model # Обычный метод # Первый параметр метода - self def check_sim(self, mobile_operator): if self.model == 'I785' and mobile_operator == 'MTS': print('Your mobile operator is MTS')
Python — Интерактивный режим
# Импортируем наш класс для работы с ним >>> from phone import Phone # Создаем экземпляр класса >>> my_phone = Phone('red', 'I785') # Обращаемся к методу check_sim() через объект my_phone >>> my_phone.check_sim('MTS') Your mobile operator is MTS
Стоит заметить, что, как правило, данная группа методов является самой многочисленной и часто используемой в сравнении со статическими методами и методами класса.
2. Статические методы
Статические методы — это обычные функции, которые помещены в класс для удобства и тем самым располагаются в области видимости этого класса. Чаще всего это какой-то вспомогательный код.
Важная особенность заключается в том, что данные методы можно вызывать посредством обращения к имени класса, создавать объект класса при этом не обязательно. Именно поэтому в таких методах не используется self — этому методу не важна информация об объектах класса.
Чтобы создать статический метод в Python, необходимо воспользоваться специальным декоратором — @staticmethod . Выглядит это следующим образом:
class Phone: # Статический метод справочного характера # Возвращает хэш по номеру модели # self внутри метода отсутствует @staticmethod def model_hash(model): if model == 'I785': return 34565 elif model == 'K498': return 45567 else: return None # Обычный метод def check_sim(self, mobile_operator): pass
Python — Интерактивный режим
>>> from phone import Phone # Вызываем статический метод model_hash, просто обращаясь к имени класса # Объект класса Phone при этом создавать не надо >>> Phone.model_hash('I785') 34565
3. Методы класса
Методы класса являются чем-то средним между обычными методами (привязаны к объекту) и статическими методами (привязаны только к области видимости). Как легко догадаться из названия, такие методы тесно связаны с классом, в котором они определены.
Обратите внимание, что такие методы могут менять состояние самого класса, что в свою очередь отражается на ВСЕХ экземплярах данного класса. Правда при этом менять конкретный объект класса они не могут (этим занимаются методы экземпляра класса).
Чтобы создать метод класса, необходимо воспользоваться соответствующим декоратором — @classmethod . При этом в качестве первого параметра такого метода передается служебное слово cls , которое в отличие от self является ссылкой на сам класс (а не на объект). Рассмотрим пример:
class Phone: def __init__(self, color, model, os): self.color = color self.model = model self.os = os # Метод класса # Принимает 1) ссылку на класс Phone и 2) цвет в качестве параметров # Создает специфический объект класса Phone(особенность объекта в том, что это игрушечный телефон) # При этом вызывается инициализатор класса Phone # которому в качестве аргументов мы передаем цвет и модель, # соответствующую созданию игрушечного телефона @classmethod def toy_phone(cls, color): toy_phone = cls(color, 'ToyPhone', None) return toy_phone # Статический метод @staticmethod def model_hash(model): pass # Обычный метод def check_sim(self, mobile_operator): pass
Python — Интерактивный режим
>>> from phone import Phone # Создаем объект игрушечный телефон # Обращаемся к методу класса toy_phone через имя класса и точку >>> my_toy_phone = Phone.toy_phone('Red') >>> my_toy_phone
- Необходимо создать специфичный объект текущего класса
- Нужно реализовать фабричный паттерн — создаём объекты различных унаследованных классов прямо внутри метода

Уровни доступа атрибутов в Python
Вам наверняка известно, что в классических языках программирования (таких как C++ и Java) доступ к ресурсам класса реализуется с помощью служебных слов public , private и protected :
- Private. Приватные члены класса недоступны извне — с ними можно работать только внутри класса.
- Public. Публичные методы наоборот — открыты для работы снаружи и, как правило, объявляются публичными сразу по-умолчанию.
- Protected. Доступ к защищенным ресурсам класса возможен только внутри этого класса и также внутри унаследованных от него классов (иными словами, внутри классов-потомков). Больше никто доступа к ним не имеет
- Если переменная/метод начинается с одного нижнего подчеркивания ( _protected_example ), то она/он считается защищенным ( protected ).
- Если переменная/метод начинается с двух нижних подчеркиваний ( __private_example ), то она/он считается приватным ( private ).

Все члены класса в Python являются публичными по умолчанию. Любой член класса может быть доступен за пределами самого класса. Вот так выглядит создание и работа с публичными ( public ) методами в Python:
class Phone: def __init__(self, color): # Объявляем публичное поле color self.color = color
>>> from phone import Phone # Создаем экземпляр класса Phone >>> phone = Phone('Grey') # Обращаемся к свойству color >>> phone.color 'Grey' # Изменяем свойство color >>> phone.color = 'Red' >>> phone.color 'Red'
Как видите, никаких проблем. Идем дальше. Как мы уже сказали, в соотвествии с соглашением чтобы сделать атрибут класса защищенным ( protected ), необходимо добавить к имени символ подчеркивания _ . Как, например, здесь:
class Phone: def __init__(self, color): # Объявляем защищенное поле _color self._color = color
Однако по факту в Python такой атрибут все равно будет доступен снаружи класса. Вы все еще можете выполнить операции, которые мы рассмотрели выше:
# Создаем экземпляр класса Phone >>> phone = Phone('Grey') # Обращаемся к защищенному свойству _color >>> phone._color 'Grey' # Изменяем защищенное свойство _color >>> phone._color = 'Red' >>> phone._color 'Red'
Иными словами, это больше вопрос ответственности программиста — он не должен работать с атрибутами, имена которых начинаются с нижнего подчёркивания _ , снаружи класса.
Аналогично, два нижних подчеркивания __ в названии свойства/метода делают его приватным ( private ). Здесь уже интереснее — получить доступ к таким атрибутам напрямую нельзя (но если очень хочется, то все равно можно — об этом чуть ниже):
class Phone: def __init__(self, color): # Объявляем приватное поле __color self.__color = color
>>> from phone import Phone >>> phone = Phone('Grey') # Пытаемся обратиться к приватному свойству и получаем ошибку >>> phone.__color Traceback (most recent call last): File "", line 1, in AttributeError: 'Phone' object has no attribute '__color'
Однако если мы заглянем в список атрибутов нашего класса, то увидим, что на самом деле наше свойство просто получило другое имя. И как вы уже наверное догадались, оно все еще доступно извне, но теперь уже по другому имени:
# Получаем список атрибутов класса, в котором находим новое имя свойства: '_Phone__color' >>> dir(phone) ['_Phone__color', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] # Обращаемся к защищенному свойству по новому имени >>> phone._Phone__color 'Grey' # Меняем значение защищенного свойства >>> phone._Phone__color = 'Blue' >>> phone._Phone__color 'Blue'
- Существует три уровня доступа к свойствам/методам класса: public, protected, private
- Физически данный механизм ограничения доступа к атрибутам класса в Python реализован слабо, что от части может противоречить одному из главных принципов ООП — инкапсуляции.
- Однако существует некоторое соглашение, по которому в Python задать уровень доступа к свойству/методу класса можно с помощью добавления к имени одного ( protected ) или двух ( private ) подчеркиваний. Ответственность за соблюдение данного соглашения ложится на плечи программистов.

Наследование в Python
Как мы уже выяснили выше, механизм наследования позволяет создать новый класс на основе уже существующего. При этом новый класс включает в себя как свойства и методы родительского класса, так и новые (собственные) атрибуты. Эти новые атрибуты и отличают свежесозданный класс от его родителя.
Для того, чтобы в Python создать новый класс с помощью механизма наследования, необходимо воспользоваться следующим синтаксисом:
class ():
Теперь давайте рассмотрим пример применения механизма наследования в действии. Перед нами класс Phone (Телефон), у которого есть одно свойство is_on и три метода:
# Родительский класс class Phone: # Инициализатор def __init__(self): self.is_on = False # Включаем телефон def turn_on(self): self.is_on = True # Если телефон включен, делаем звонок def call(self): if self.is_on: print('Making call. ')
В результате объект такого класса получит следующий набор атрибутов:
>>> my_phone = Phone() >>> dir(my_phone) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'call', 'is_on', 'turn_on']
Среди данной совокупности атрибутов нас больше всего интересуют пользовательские свойства и методы: ‘__init__’ , ‘call’ , ‘is_on’ , ‘turn_on’
А теперь предположим, что мы захотели создать новый класс — MobilePhone (Мобильный телефон). Хоть этот класс и новый, но это по-прежнему телефон, а значить — его все так же можно включить и по нему можно позвонить. А раз так, то нам нет смысла реализовывать этот функционал заново, а можно просто унаследовать его от класса Phone . Выглядит это так:
class Phone: def __init__(self): self.is_on = False def turn_on(self): self.is_on = True def call(self): if self.is_on: print('Making call. ') # Унаследованный класс class MobilePhone(Phone): # Добавляем новое свойство battery def __init__(self): super().__init__() self.battery = 0 # Заряжаем телефон на величину переданного значения def charge(self, num): self.battery = num print(f'Charging battery up to . %')
Как вы видите, в новом классе добавились свойство battery и метод charge() . При этом мы помним, что это класс-потомок Phone , а значит от унаследовал и его функционал тоже. Создадим объект нового класса и посмотрим список его атрибутов:
>>> my_mobile_phone = MobilePhone() >>> dir(my_mobile_phone) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'battery', 'call', 'charge', 'is_on', 'turn_on']
Теперь мы видим, что пользовательские атрибуты состоят из унаследованных ( ‘is_on’ , ‘call’ , ‘turn_on’ ) и новых ( ‘__init__’ , ‘battery’ , ‘charge’ ). Все они теперь принадлежат классу MobilePhone . Пример использования:
# Импортируем оба класса >>> from phone import Phone, MobilePhone # Создаем объект класса MobilePhone >>> my_mobile_phone = MobilePhone() # Включаем телефон и делаем звонок >>> my_mobile_phone.turn_on() >>> my_mobile_phone.call() Making call. # Заряжаем мобильный телефон >>> my_mobile_phone.charge(76) Charging battery up to . 76%
Что такое super?
Как вы могли заметить, в инициализаторе (метод __init__ ) наследуемого класса вызывается метод super() . Что это за метод и зачем он нужен?
Главная задача этого метода — дать возможность наследнику обратиться к родительскому классу. В классе родителе Phone есть свой инициализатор, и когда в потомке MobilePhone мы так же создаем инициализатор (а он нам действительно нужен, так как внутри него мы хотим объявить новое свойство) — мы его перегружаем. Иными словами, мы заменяем родительский метод __init__() собственным одноименным методом. Это чревато тем, что родительский метод просто в принципе не будет вызван, и мы потеряем его функционал в классе наследнике. В конкретном случае, потеряем свойство is_on .
- Внутри инициализатора класса-наследника вызвать инициализатор родителя (для этого вызываем метод super().__init__() )
- А затем просто добавить новый функционал
def __init__(self):
____super().__init__()
____self.battery = 0
Обратите внимание, что вызывать родительский метод необходимо в первую очередь.

Полиморфизм в Python
Как вы уже знаете, полиморфизм позволяет перегружать одноименные методы родительского класса в классах-потомках. Что в свою очередь дает возможность использовать перегруженный метод в случаях, когда мы еще не знаем, для какого именно класса он будет вызван. Мы просто указываем имя метода, а объект класса, к которому он будет применен, определится по ходу выполнения программы. Чтобы стало более понятно, давайте рассмотрим пример:
# Родительский класс class Phone: def __init__(self): self.is_on = False def turn_on(self): pass def call(self): pass # Метод, который выводит короткую сводку по классу Phone def info(self): print(f'Class name: ') print(f'If phone is ON: ') # Унаследованный класс class MobilePhone(Phone): def __init__(self): super().__init__() self.battery = 0 # Такой же метод, который выводит короткую сводку по классу MobilePhone # Обратите внимание, что названия у методов совпадают - оба метода называются info() # Однако их содержимое различается def info(self): print(f'Class name: ') print(f'If mobile phone is ON: ') print(f'Battery level: ') # Демонстрационная функция # Создаем список из классов # В цикле перебираем список и для каждого элемента списка(а элемент - это класс) # Создаем объект и вызываем метод info() # Главная особенность: запись object.info() не дает информацию об объекте, для которого будет вызван метод info() # Это может быть объект класса Phone, а может - объект класса MobilePhone # И только в момент исполнения кода становится ясно, для какого именно объекта нужно вызывать метод info() def show_polymorphism(): for item in [Phone, MobilePhone]: print('-------') object = item() object.info()
