Наследование
![]()
Наследование — это способность объекта принимать одну или несколько характеристик от других классов объектов, обычно переменных или функций-членов. Можно провести аналогию между этим понятием и наследственностью как передачей характерных черт от родителей к потомству. Например, у ребенка могут быть глаза отца и улыбка матери. Глаза отца и улыбка матери — это черты, которые ребенок наследует от своих родителей. Они выглядят/проявляются одинаково у родителей и потомства.
Информатика использует понятие наследования при создании классов, между которыми устанавливаются отношения “род-вид”. В отношениях “род-вид” один объект связан с другим объектом. Например, собака — это домашнее животное, сельдерей — овощ, а Марс — планета. В отношениях “род-вид” есть две сущности: родитель и ребенок. Родитель в этих отношениях является общей версией ребенка. Собака (ребенок) — это домашнее животное (родитель). В информатике мы называем родительский класс в отношениях “род-вид” “суперклассом”, а дочерний — “подклассом”. Подкласс наследует методы и/или переменные от суперкласса. Теперь напишем код для отношений dog/pet (собака/домашнее животное).
Создание подкласса
Обратите внимание на то, что в приведенных примерах для создания класса Dog мы заключаем Pet в скобки в объявлении класса: Dog(Pet) . Использование этого синтаксиса позволяет сообщить Python, что класс, который мы создаем, является подклассом суперкласса Pet. Поскольку ни один из методов и переменных в классе Pet не является закрытым, подкласс Dog сможет получить доступ ко всем элементам класса Pet. Именно так экземпляр собаки способен вызывать методы feedme() и eat() .
Некоторые атрибуты класса могут быть созданы закрытыми. Создать переменную закрытой означает, что этот атрибут не будет доступен подклассу. Имейте это в виду при проектировании классов!
Полиморфизм
Теперь, когда вы получили представление о наследовании, можно перейти к понятию “полиморфизм”. Полиморфизм — это способность чего-либо иметь несколько форм. Вернемся к предыдущему примеру. Все домашние животные в конечном итоге нуждаются в еде, но способ, которым они питаются, может отличаться. Например, попугай будет есть, клюя корм для птиц, в то время как собака будет есть, пережевывая собачий корм. И попугай, и собака — домашние животные, которые едят пищу, но разница в том, что они едят разную пищу по-разному. Взглянем на пример в коде.
В этом примере собака и попугай переопределяют метод eat(self,food_name) . Переопределение метода означает, что метод в подклассе переопределяет метод, указанный в суперклассе. В этом суть полиморфизма: один и тот же метод, но разные характеристики в зависимости от класса.
Заключение
Мы кратко рассмотрели наследование и полиморфизм в Python. Поначалу эти понятия, возможно, будет непросто усвоить. Лучший способ справиться с этим — разработать классы, которые отражают сущности реального мира, а затем идентифицировать свойственные им отношения “род-вид”.
Понимание наследования классов в Python 3
Объектно-ориентированное программирование создает многократно используемые шаблоны кода для сокращения избыточности в проектах разработки. Одним из способов, которым объектно-ориентированное программирование достигает кода, пригодного для повторного использования, является наследование, когда один подкласс может использовать код из другого базового класса.
В этом руководстве будут рассмотрены некоторые из основных аспектов наследования в Python, включая то, как работают родительские классы и дочерние классы, как переопределять методы и атрибуты, как использовать функцию super() и как использовать множественное наследование. .
Что такое наследование?
Inheritance — это когда класс использует код, созданный внутри другого класса. Если мы думаем о наследовании с точки зрения биологии, мы можем думать о ребенке, унаследовавшем определенные черты от своего родителя. То есть ребенок может наследовать рост или цвет глаз родителя. Дети также могут иметь одинаковую фамилию со своими родителями.
Классы, называемыеchild classes илиsubclasses, наследуют методы и переменные отparent classes илиbase classes.
Мы можем представить себе родительский класс с именем Parent , у которого естьclass variables для last_name , height и eye_color , которые наследует дочерний класс Child из Parent .
Поскольку подкласс Child наследуется от базового класса Parent , класс Child может повторно использовать код Parent , что позволяет программисту использовать меньше строк кода и уменьшить избыточность .
Родительские классы
Родительские или базовые классы создают шаблон, на котором могут основываться дочерние или подклассы. Родительские классы позволяют нам создавать дочерние классы посредством наследования без необходимости каждый раз переписывать один и тот же код. Любой класс может быть превращен в родительский класс, поэтому каждый из них является самостоятельным полностью функциональным классом, а не просто шаблоном.
Допустим, у нас есть общий родительский класс Bank_account , у которого есть дочерние классы Personal_account и Business_account . Многие методы между личными и корпоративными счетами будут похожи, например методы снятия и внесения денег, поэтому они могут принадлежать к родительскому классу Bank_account . Подкласс Business_account будет иметь специфические для него методы, включая, возможно, способ сбора бизнес-записей и форм, а также переменную employee_identification_number .
Точно так же класс Animal может иметь методы eating() и sleeping() , а подкласс Snake может включать свои собственные специфические методы hissing() и slithering() .
Давайте создадим родительский класс Fish , который мы позже будем использовать для создания типов рыб в качестве его подклассов. Каждая из этих рыб будет иметь имена и фамилии в дополнение к характеристикам.
Мы создадим новый файл с именем fish.py и начнем с __init__() constructor method, который мы заполним переменными класса first_name и last_name для каждого объекта Fish . или подкласс.
Мы инициализировали нашу переменную last_name строкой «Fish» , потому что мы знаем, что у большинства рыбок будет эта фамилия.
Давайте также добавим некоторые другие методы:
Мы добавили методы swim() и swim_backwards() в класс Fish , чтобы каждый подкласс также мог использовать эти методы.
Поскольку большинство рыб, которые мы будем создавать, считаютсяbony fish (так как у них скелет сделан из кости), а неcartilaginous fish (поскольку у них скелет сделан из хряща) , мы можем добавить еще несколько атрибутов к методу __init__() :
Создание родительского класса следует той же методологии, что и любой другой класс, за исключением того, что мы думаем о том, какие методы дочерние классы смогут использовать после их создания.
Детские классы
Дочерние или подклассы — это классы, которые будут наследоваться от родительского класса. Это означает, что каждый дочерний класс сможет использовать методы и переменные родительского класса.
Например, дочерний класс Goldfish , который является подклассом класса Fish , сможет использовать метод swim() , объявленный в Fish , без необходимости его объявления.
Мы можем думать о каждом дочернем классе как о классе родительского класса. То есть, если у нас есть дочерний класс с именем Rhombus и родительский класс с именем Parallelogram , мы можем сказать, что a Rhombus is a Parallelogram , как и Goldfish is a Fish .
Первая строка дочернего класса выглядит немного иначе, чем не дочерние классы, так как вы должны передать родительский класс в дочерний класс в качестве параметра:
Класс Trout является дочерним по отношению к классу Fish . Мы знаем это благодаря включению в скобки слова Fish .
С дочерними классами мы можем добавить дополнительные методы, переопределить существующие родительские методы или просто принять родительские методы по умолчанию с ключевым словом pass , что мы и сделаем в этом случае:
Теперь мы можем создать объект Trout без необходимости определять какие-либо дополнительные методы.
Мы создали объект Trout terry , который использует каждый из методов класса Fish , хотя мы не определили эти методы в дочернем классе Trout . Нам нужно было только передать значение «Terry» переменной first_name , потому что все другие переменные были инициализированы.
Когда мы запустим программу, мы получим следующий вывод:
Далее, давайте создадим еще один дочерний класс, который включает в себя собственный метод. Назовем этот класс Clownfish , и его специальный метод позволит ему жить с морским анемоном:
Затем давайте создадим объект Clownfish , чтобы увидеть, как это работает:
Когда мы запустим программу, мы получим следующий вывод:
Выходные данные показывают, что объект Clownfish casey может использовать методы Fish __init__() и swim() , а также метод своего дочернего класса live_with_anemone() с.
Если мы попытаемся использовать метод live_with_anemone() в объекте Trout , мы получим ошибку:
Это связано с тем, что метод live_with_anemone() принадлежит только дочернему классу Clownfish , а не родительскому классу Fish .
Дочерние классы наследуют методы родительского класса, к которому он принадлежит, поэтому каждый дочерний класс может использовать эти методы в программах.
Переопределение родительских методов
До сих пор мы рассматривали дочерний класс Trout , который использовал ключевое слово pass для наследования всего поведения родительского класса Fish , и еще один дочерний класс Clownfish , который унаследовал все поведения родительского класса, а также создал свой собственный уникальный метод, специфичный для дочернего класса. Однако иногда нам захочется использовать некоторые из поведений родительского класса, но не все. Когда мы меняем методы родительского класса, мыoverrideих.
При создании родительских и дочерних классов важно помнить о разработке программы, чтобы переопределение не приводило к ненужному или избыточному коду.
Мы создадим дочерний класс Shark родительского класса Fish . Поскольку мы создали класс Fish с идеей, что мы будем создавать в основном костлявую рыбу, нам нужно будет внести изменения для класса Shark , который вместо этого является хрящевой рыбой. С точки зрения разработки программы, если бы у нас было более одной не костистой рыбы, мы, скорее всего, хотели бы создать отдельные классы для каждого из этих двух типов рыб.
У акул, в отличие от костистых рыб, вместо кости есть скелеты из хряща. У них также есть веки, и они не могут плавать назад. Акулы могут, однако, маневрировать в обратном направлении, погружаясь.
В свете этого мы переопределим метод конструктора __init__() и метод swim_backwards() . Нам не нужно изменять метод swim() , поскольку акулы — это рыба, которая умеет плавать. Давайте посмотрим на этот дочерний класс:
Мы переопределили инициализированные параметры в методе __init__() , так что переменная last_name теперь установлена равной строке «Shark» , переменная skeleton теперь установлена равной строка «cartilage» , а для переменной eyelids теперь установлено логическое значение True . Каждый экземпляр класса также может переопределять эти параметры.
Метод swim_backwards() теперь выводит строку, отличную от строки в родительском классе Fish , потому что акулы не могут плавать назад так, как это могут делать костлявые рыбы.
Теперь мы можем создать экземпляр дочернего класса Shark , который по-прежнему будет использовать метод swim() родительского класса Fish :
Когда мы запустим этот код, мы получим следующий вывод:
Дочерний класс Shark успешно переопределил методы __init__() и swim_backwards() родительского класса Fish , а также унаследовал метод swim() родительского класса.
Когда будет ограниченное число дочерних классов, которые являются более уникальными, чем другие, переопределение методов родительского класса может оказаться полезным.
Функция super()
С помощью функции super() вы можете получить доступ к унаследованным методам, которые были перезаписаны в объекте класса.
Когда мы используем функцию super() , мы вызываем родительский метод в дочерний метод, чтобы использовать его. Например, мы можем захотеть переопределить один аспект родительского метода с определенной функциональностью, но затем вызвать остальную часть исходного родительского метода, чтобы завершить метод.
В программе, которая оценивает учащихся, мы можем захотеть иметь дочерний класс для Weighted_grade , который наследуется от родительского класса Grade . В дочернем классе Weighted_grade мы можем захотеть переопределить метод calculate_grade() родительского класса, чтобы включить функциональность для вычисления взвешенной оценки, но при этом сохранить остальную функциональность исходного класса. Вызвав функцию super() , мы сможем этого добиться.
Функция super() чаще всего используется в методе __init__() , потому что именно здесь вам, скорее всего, потребуется добавить некоторую уникальность к дочернему классу, а затем завершить инициализацию от родительского.
Чтобы увидеть, как это работает, давайте изменим наш дочерний класс Trout . Поскольку форель обычно является пресноводной рыбой, давайте добавим переменную water к методу __init__() и установим ее равной строке «freshwater» , но затем сохраним остальные переменные и параметры родительского класса:
Мы переопределили метод __init__() в дочернем классе Trout , предоставив другую реализацию __init__() , которая уже определена его родительским классом Fish . В методе __init__() нашего класса Trout мы явно вызвали метод __init__() класса Fish .
Поскольку мы переопределили метод, нам больше не нужно передавать first_name в качестве параметра в Trout , и если бы мы действительно передали параметр, мы бы вместо этого сбросили freshwater . Поэтому мы инициализируем first_name , вызывая переменную в нашем экземпляре объекта.
Теперь мы можем вызывать инициализированные переменные родительского класса, а также использовать уникальную дочернюю переменную. Давайте использовать это в экземпляре Trout :
Выходные данные показывают, что объект terry дочернего класса Trout может использовать как специфичную для ребенка переменную __init__() water , так и возможность вызова Fish родительские переменные __init__() для first_name , last_name и eyelids .
Встроенная функция Python super() позволяет нам использовать методы родительского класса даже при переопределении определенных аспектов этих методов в наших дочерних классах.
Множественное наследование
Multiple inheritance — это когда класс может наследовать атрибуты и методы более чем одного родительского класса. Это может позволить программам уменьшить избыточность, но может также внести определенную сложность, а также двусмысленность, так что это должно быть сделано с учетом общего дизайна программы.
Чтобы показать, как работает множественное наследование, давайте создадим дочерний класс Coral_reef , а не унаследованный от класса Coral и класса Sea_anemone . Мы можем создать метод в каждом из них, а затем использовать ключевое слово pass в дочернем классе Coral_reef :
Класс Coral имеет метод под названием community() , который печатает одну строку, а класс Anemone имеет метод под названием protect_clownfish() , который печатает другую строку. Затем мы вызываем оба класса в наследованиеtuple. Это означает, что Coral наследуется от двух родительских классов.
Давайте теперь создадим экземпляр объекта Coral :
Объект great_barrier установлен как объект CoralReef и может использовать методы обоих родительских классов. Когда мы запустим программу, мы увидим следующий вывод:
Вывод показывает, что методы из обоих родительских классов эффективно использовались в дочернем классе.
Множественное наследование позволяет нам использовать код более чем одного родительского класса в дочернем классе. Если один и тот же метод определен в нескольких родительских методах, дочерний класс будет использовать метод первого родителя, объявленного в его списке кортежей.
Хотя это может быть эффективно использовано, множественное наследование должно осуществляться с осторожностью, чтобы наши программы не становились двусмысленными и трудными для понимания другими программистами.
Заключение
В этом руководстве было рассмотрено создание родительских и дочерних классов, переопределение родительских методов и атрибутов в дочерних классах с использованием функции super() и разрешение дочерним классам наследовать от нескольких родительских классов.
Наследование в объектно-ориентированном кодировании может позволить придерживаться СУХОГО (не повторяйся) принципа разработки программного обеспечения, что позволяет делать больше с меньшими затратами кода и повторений. Наследование также заставляет программистов задуматься о том, как они проектируют создаваемые ими программы, чтобы обеспечить эффективность и ясность кода.
Наследование в Python
Наследование — одна из концепций объектно-ориентированного программирования (ООП).
Как было сказано выше, наследование позволяет объявить класс, который либо не отличается от существующего, либо содержит минимальные изменения. Новый класс называется дочерним, а тот, у которого он наследует функционал — родительским.
Синтаксис
Особенность наследования заключается в том, что оно позволяет не просто создать дубликат класса, но и расширить его функционал. Это очень полезно, потому что наследование позволяет повторно использовать уже написанный код.
Пример использования наследования
Полигон — замкнутая геометрическая фигура. У полигона 3 и более сторон.
Давайте объявим класс Polygon :
В этом классе объявлено несколько переменных. Одна хранит количество сторон — n . Вторая, sides — это список, в нём находятся размеры сторон.
Метод inputSides() принимает размер каждой стороны, а dispSides() выводит их на экран.
Треугольник — это полигон с 3 сторонами. Теперь мы можем создать класс Triangle , который наследует весь функционал Polygon . Благодаря этому все атрибуты класса Polygon становятся доступны в Triangle .
Так что нам не нужно объявлять все переменные и методы снова. Давайте создадим класс Triangle :
В классе есть и собственный метод findArea() . Он вычисляет площадь треугольника и выводит ее на экран. Попробуем запустить нашу программу:
Как видите, мы не объявляли методы inputSides() и dispSides() в классе Triangle . Но вот использовать их мы можем!
Если какой-либо атрибут не найдется в дочернем классе, Python пойдет искать в родительской. Этот поиск происходит рекурсивно, если родительский класс одного класса является дочерним для другого.
Переопределение методов
Стоит заметить, что в примере метод __init__() был объявлен в обоих классах — и в Triangle , и в Polygon . Здесь и происходит переопределение классов. То есть, метод в дочернем классе переопределяет тот же самый метод из родительского класса. Это значит, что __init__() в Triangle становится предпочтительнее __init__() в Polygon .
При переопределении метода родительского класса нужно стремиться к его расширению, а не простому копированию. Это, например, происходит при вызове метода в родительском классе из дочернего (вызов P olygon.__init__() из __init__() в Triangle ).
Лучше всего использовать встроенную функцию super() . Например, super().__init__(3) эквивалентно вызову Polygon.__init__(self, 3) . Старайтесь использовать именно этот способ.
Проверка наследования
Для проверки наследования можно использовать две функции: isinstance() и issubclass() .
Функция isinstance() возвращает True , если объект является экземпляром класса или других производных от него классов. Каждый класс в Python является дочерним для какого-либо базового класса.
subclass() же используется для проверки, наследуется ли какой-либо класс от другого.
Урок 6. Принципы ООП. Классы, объекты, поля и методы. Уровни доступа.
Поговорим про основные принципы объектно-ориентированного программирования: абстракцию, инкапсуляцию, наследование и полиморфизм. Научимся создавать классы и объекты классов в Python. Рассмотрим, чем отличаются понятия поля, свойства, методы и атрибуты класса. Изучим особенности организации уровней доступа к атрибутам: Public, Protected и Private.

Курс «Программирование на Python»
Урок 6.
Принципы ООП. Классы, объекты, поля и методы. Уровни доступа.

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

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

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

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

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

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

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

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

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


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

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

1. Встроенные атрибуты
Это важно
В теории ООП конструктор класса — это специальный блок инструкций, который вызывается при создании объекта. При работе с питоном может возникнуть мнение, что метод __init__(self) — это и есть конструктор, но это не совсем так. На самом деле, при создании объекта в Python вызывается метод __new__ (cls, *args, **kwargs) и именно он является конструктором класса.
Также обратите внимание, что __new__() — это метод класса, поэтому его первый параметр cls — ссылка на текущий класс. В свою очередь, метод __init__() является так называемым инициализатором класса. Именно этот метод первый принимает созданный конструктором объект. Как вы уже, наверное, не раз замечали, метод __init__() часто переопределяется внутри класса самим программистом. Это позволяет со всем удобством задавать параметры будущего объекта при его создании.
2. Пользовательские атрибуты
Это атрибуты, которые непосредственно составляют основной функционал класса. Если служебные атрибуты наследуются от базового класса object , то пользовательские — пишутся программистом во время реализации начинки класса и дальнейшей работы с ним.
Список атрибутов класса / объекта можно получить с помощью команды dir() . Если взять самый простой класс:
- Атрибутами называем совокупность полей и методов класса / объекта.
- Атрибуты делятся на встроенные и пользовательские.
- Все классы в Python имеют общий родительский класс — он называется object .
- Класс object предоставляет всем своим потомкам набор служебных атрибутов (как переменных (например, __dict__ и __doc__ ), так и методов (например, __str__ ) ).
- Многие из служебных атрибутов можно переопределить внутри своего класса.
- Поля и методы, которые описываются программистом в теле класса, являются пользовательскими и добавляются в общий список атрибутов наряду со встроенными атрибутами.

- Статические поля
- Динамические поля

1. Статические поля (они же переменные или свойства класса)
2. Динамические поля (переменные или свойства экземпляра класса)
Что такое 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.<имя_переменной> внутри одного из методов.
- Экземпляр класса сочетает в себе совокупность как статических (уровня класса), так и динамических (уровня самого экземпляра) полей.
- Значения динамических переменных для разных объектов класса могут (и чаще всего так и делают) различаться.

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

1. Методы экземпляра класса (Обычные методы)
2. Статические методы
Статические методы — это обычные функции, которые помещены в класс для удобства и тем самым располагаются в области видимости этого класса. Чаще всего это какой-то вспомогательный код.
Важная особенность заключается в том, что данные методы можно вызывать посредством обращения к имени класса, создавать объект класса при этом не обязательно. Именно поэтому в таких методах не используется self — этому методу не важна информация об объектах класса.
Чтобы создать статический метод в Python, необходимо воспользоваться специальным декоратором — @staticmethod . Выглядит это следующим образом:
3. Методы класса
Методы класса являются чем-то средним между обычными методами (привязаны к объекту) и статическими методами (привязаны только к области видимости). Как легко догадаться из названия, такие методы тесно связаны с классом, в котором они определены.
Обратите внимание, что такие методы могут менять состояние самого класса, что в свою очередь отражается на ВСЕХ экземплярах данного класса. Правда при этом менять конкретный объект класса они не могут (этим занимаются методы экземпляра класса).
Чтобы создать метод класса, необходимо воспользоваться соответствующим декоратором — @classmethod . При этом в качестве первого параметра такого метода передается служебное слово cls , которое в отличие от self является ссылкой на сам класс (а не на объект). Рассмотрим пример:
- Необходимо создать специфичный объект текущего класса
- Нужно реализовать фабричный паттерн — создаём объекты различных унаследованных классов прямо внутри метода

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

Иными словами, это больше вопрос ответственности программиста — он не должен работать с атрибутами, имена которых начинаются с нижнего подчёркивания _ , снаружи класса.
Аналогично, два нижних подчеркивания __ в названии свойства/метода делают его приватным ( private ). Здесь уже интереснее — получить доступ к таким атрибутам напрямую нельзя (но если очень хочется, то все равно можно — об этом чуть ниже):
- Существует три уровня доступа к свойствам/методам класса: public, protected, private
- Физически данный механизм ограничения доступа к атрибутам класса в Python реализован слабо, что от части может противоречить одному из главных принципов ООП — инкапсуляции.
- Однако существует некоторое соглашение, по которому в Python задать уровень доступа к свойству/методу класса можно с помощью добавления к имени одного ( protected ) или двух ( private ) подчеркиваний. Ответственность за соблюдение данного соглашения ложится на плечи программистов.

Как мы уже выяснили выше, механизм наследования позволяет создать новый класс на основе уже существующего. При этом новый класс включает в себя как свойства и методы родительского класса, так и новые (собственные) атрибуты. Эти новые атрибуты и отличают свежесозданный класс от его родителя.
Для того, чтобы в Python создать новый класс с помощью механизма наследования, необходимо воспользоваться следующим синтаксисом:
Теперь давайте рассмотрим пример применения механизма наследования в действии. Перед нами класс Phone (Телефон), у которого есть одно свойство is_on и три метода:
- Инициализатор: __init__()
- Включение: turn_on()
- Звонок: call()
Среди данной совокупности атрибутов нас больше всего интересуют пользовательские свойства и методы: ‘__init__’ , ‘call’ , ‘is_on’ , ‘turn_on’
А теперь предположим, что мы захотели создать новый класс — MobilePhone (Мобильный телефон). Хоть этот класс и новый, но это по-прежнему телефон, а значить — его все так же можно включить и по нему можно позвонить. А раз так, то нам нет смысла реализовывать этот функционал заново, а можно просто унаследовать его от класса Phone . Выглядит это так:
Что такое super?
Как вы могли заметить, в инициализаторе (метод __init__ ) наследуемого класса вызывается метод super() . Что это за метод и зачем он нужен?
Главная задача этого метода — дать возможность наследнику обратиться к родительскому классу. В классе родителе Phone есть свой инициализатор, и когда в потомке MobilePhone мы так же создаем инициализатор (а он нам действительно нужен, так как внутри него мы хотим объявить новое свойство) — мы его перегружаем. Иными словами, мы заменяем родительский метод __init__() собственным одноименным методом. Это чревато тем, что родительский метод просто в принципе не будет вызван, и мы потеряем его функционал в классе наследнике. В конкретном случае, потеряем свойство is_on .
- Внутри инициализатора класса-наследника вызвать инициализатор родителя (для этого вызываем метод super().__init__() )
- А затем просто добавить новый функционал
def __init__(self):
____super().__init__()
____self.battery = 0
Обратите внимание, что вызывать родительский метод необходимо в первую очередь.