Python - Объектно Ориентированное Программирование (ООП)


Python - Объектно Ориентированное Программирование (ООП)

В данной статье даются основы ООП в питоне

В python всё - объекты.

*Аудитория в шоке, особо нервные дамочки падают в обморок*
  • Числа - объекты
  • Строки - объекты
  • Списки - объекты
  • Классы - объекты
  • ...

Если говорить просто, то "объекты" - это некая структура.

И было слово...

Чтобы создавать объекты, используются "конструкторы" или классы.
  • Класс - это схема, описывающая нашу структуру, возможные внутри неё данные и присущие ей методы.
  • Метод - это функция. Т.е. метод объекта - это функция, описанная внутри объекта, и присущая этому объекту. Метод - это функция, которая действует на объекты данного вида. Для удобства у разных видов объектов могут быть методы с одинаковыми именами, работающие по разному, но схожим образом.
  • Экземпляр - это конкретный объект, созданный из класса.

Рассмотрим пример

Список:
L = [1, 2, 3, 4]

  • Список (List) - это класс объекта
  • Переменная L содержит экземпляр объекта (конкретно список [1, 2, 3, 4])
  • append(), sort() - методы объекта, т.е. функции, которые можно применить к его экземплярам.

Пока звучит не очень сложно.
Но по прежнему не понятно зачем это нужно. =)

1 что приходит на ум - абстракция. Мы не думаем о том как устроен объект, а думаем о том что мы можем с ним сделать.
Например, возьмём функцию dir() и передадим экземпляр объекта список в неё как аргумент:
dir(L)

методы списка (dir)
На выходе получим большой список. Большой список методов для списка.
У нас есть 2 пути:
  • опробовать некоторые из них (возможно, пытаясь передать какие-то атрибуты)
  • найти их описание в google по имени

Но в любом случае мы узнаем о том, "что можно делать со списком", не изучая то как он устроен.

При этом если мы создадим свою структуру данных, свой класс со своими объектами, то у них могут быть одноимённые методы (например, если они ведут себя аналогично).

Например, у строк есть несколько методов, одноимённых методом списков:
методы строки (dir)
Метод index() является одним из них.
Рассмотрим как он работает для наших примеров:
поиск по индексу для списка и строки


Легко заметить, что некоторые методы в списке имели по 2 подчёркивания с обеих сторон, например:
__len__
Это "стандартный" метод. Фактически, когда мы используем функцию len(), например:
определение длинны списка и строки (len)
то на самом деле вызывается метод __len__ соответствующего объекта.

Почему?
Всё потому же - если мы будем внутри функции языка len описывать как вычислять длину любого объекта, то это будет очень много кода. Да и для новых классов объектов (например, numpy.array) эта функция не будет работать.
А так у каждого класса внутри будет краткое описание того как это работает.

Так же это позволяет переопределить поведение некоторых операторов.
Например, что будет, если мы напишем:
Что будет если сложить строку и список в python?
Фактически будет вызван метод add - s.__add__(L)

В нашем примере мы получим ошибку:
Если в python сложить строку и список, то получится ошибка =)

Но некоторые классы объектов вполне могут принимать на вход "чужака" (не всякого конечно):
а numpy array и список в python сложить можно (они должны быть одной длины и сложатся поэлементно)

А ещё когда мы пишем
print L

, то на самом деле вызывается метод __str__, чтобы преобразовать список L в строку для печати.

Итак, 
  • класс - это инструкция, по которой собирается конкретный объект
  • экземпляр - это конкретный объект
  • а метод - это функция, присущая конкретному классу объектов.


И это всё?

Нет. =)

Когда мы говорим, что есть 2 экземпляра объектов список:
  • [1, 2, 3, 4]
  • [1, 2, 3]

то очевидно, что они содержат разные данные.
Т.е. внутри объектов помимо методов есть данные, и хранятся они в атрибутах.

Примером атрибута может быть shape для numpy:
атрибут shape для numpy array
Мы просто создали вектор, передав в него данные. И при этом вычислился его размер.
Логично, для больших матриц в векторов проще хранить внутри 1 переменную с натуральным числом, чем каждый раз проходить по всем данным и тратить время для вычислений.


Давайте попрактикуемся

Создадим пустой класс, который ничего не делает и ничего не хранит:
python - пример пустого класса

Теперь создадим 1й метод у нашего класса.
Для этого создадим функцию, внутри метода. Она должна принимать по крайней мере 1 аргумент - self - это экземпляр того объекта, методов которого функция будет.

python - пример класса с 1 методом, выводящим на печать строку

Если мы хотим передавать в метод какие-то параметры, то просто зададим их после self, как и обычные параметры функции:
Python - пример класса с простым методом, принимающим 1 аргумент

Это всё здорово, но пока особой разницы между методом и функцией нет.
Можно конечно рассматривать класс как некий контейнер для централизованного хранения функций.
Но обычно всё же класс подразумевает ещё и хранение данных в атрибутах.

Синтаксис для атрибутов аналогичен синтаксису методов (только после атрибута не надо ставить круглые скобки):
  • obj.method()
  • obj.atr

Давайте создадим атрибут у экземпляра объекта:
создаём атрибут у уже существующего экземпляра объекта в python

Вполне очевидно, что если я создам 2й экземпляр этого же класса, и присвою уже в нём атрибуту с таким же именем какое-то значение, то у разных экземпляров в одноимённом атрибуте будут разные значения:
добавляем 2 разных атрибута в 2 разных экземпляра одного объекта в python

Так же я могу определить переменную внутри класса и она станет атрибутов всех экземпляров этого класса.
Естественно, это не отменит возможность принудительного переопределения соответствующего атрибута у какого-то экземпляра:
создаём атрибут в классе python
Для экземпляров B и C значение атрибута равно значению по-умолчанию (списку), а для экземпляров E и A переопределено (на число и строку).

Пока пользоваться классами не очень удобно:
  • надо определить класс
  • создать объект
  • обратиться к каждому из атрибутов, записав туда кастомные данные
  • обратиться к каждому объекту, передав туда кастомные аргументы

Однако, процесс можно усовершенствовать в помощью конструктора (на самом деле инициализатора, но мы в этой статье не будем углубляться в разницу). Конструктор вызывается автоматически при создании экземпляра.

Для этого служит метод __init__().

Перепишем наш класс и убедимся, что его работа не изменилась:
добавляем создание атрибута в инициализатор (конструктор) простого класса Python
Важно:
  • метод __init__(), как и другие методы класса должен принимать как минимум 1 аргумент self
  • к аргументам объекта можно обращаться из метода как аргументам self (например, self.arg)
  • Если аргумент создаётся конструктором __init__, то его не нужно описывать в классе как отдельную переменную

Если для конструктора __init__ мы зададим какие-то аргументы кроме self, то их можно будет передать в конструктор при создании экземпляра объекта, и сразу записать в аргументы, если необходимо.

Давайте создадим чуть более осмысленный класс.
Пусть это будет класс "Точка". Точка у нас будет характеризоваться 2 координатами (x, y).
И сразу зададим метод для нашей точки, вычисляющий расстояние от неё до другой точки (для этого воспользуемся теоремой Пифагора):
Класс создания точки на плоскости с методом вычисления расстояния между точек

Здесь метод dist принимает 2 аргумента: экземпляр текущего объекта и ещё одного, между которыми необходимо найти расстояние.
Поскольку метод применяется к текущему экземпляру, то при вызове метода в скобках я указываю только 2ю точку).

Если я хочу передавать в мой метод аргументы привычным образом (как аргументы в скобках по порядку), то мне необходимо указать полный путь до метода. Он начнётся с имени класса:
вызов метода из класса Python напрямую с передачей 2 объектов в качестве аргументов
Обратите внимание, что 2й способ обращения позволяет использовать классы как хранилище функций даже для стандартных типов данных.

Вот пример такого "хранилища":
Класс как хранилище функций - простой пример

Заметим, что если мы попытаемся напечатать экземпляр объекта (а не его атрибуты, как раньше), то ничего хорошего не получим:
Вывод на печать экземпляра объекта без метода __str__

Мы можем понять экземпляром какого класса объект является и на какую область памяти смотрит указатель, но в работе это довольно бесполезно.
Согласитесь, печатая экземпляр списка мы получаем список - это удобно.
Воспользуемся стандартным методом __str__, который вызывается при его печати с помощью print:
Добавление метода __str__ для корректной печати экземпляров объекта в python
Интересный эффект, который можно заметить - после пересоздания класса ранее созданные объекты не меняют своего поведения.

Дело в том, что класс - это тоже объект.

С точки зрения python мы создали 2 объекта. И на базе 1-ого создали несколько экземпляров.
Поэтому чтобы новые методы добавились у объектов, объекты придётся пересоздать.


Давайте заведём ещё 1 экземпляр объекта с точно такими же координатами, что и первый.
В бытовом понимании 2 такие точки "равны". Но что будет, если мы их сравним?
Сравнение двух экземпляров одного объекта с идентичными атрибутами в python
С точки зрения python это 2 разных объекта, а потому они НЕ равны.

Чтобы это исправить можно написать стандартный метод __eq__:
Метод для сравнения двух экземпляров одного класса в python
Замечу, что если мы заведём по новому атрибуту у наших точек (например, цвету), то операция сравнения их учитывать не будет:

метод класса учитывает не все атрибуты при сравнении в python (2 экземпляра имеют разные значения одного атрибута, но код считает их равными друг другу)

Геттеры и Сеттеры

Идея геттеров и сеттеров заключается в том, что передавать в экземпляр класса значения атрибутов явно - довольно опасная идея.
Т.к. питон язык с динамический типизацией, то мы можем передать любой тип данных в переменную с одним и тем же именем внутри разных экземпляров (мы так и делали раньше и считали это даже преимуществом)

Но иногда при добавлении атрибута имеет смысл провести валидацию. Или иметь метод который не перезатирает значение, а добавляет (примером такого метода является append для списков).

Добавим в наш класс 2 новых метода:
  • сеттер - setColor - проверяет, что передаётся строка и записывает её в атрибут color экземпляра. Если передан другой тип данных, возвращает ошибку.
  • геттер - getColor - возвращает значение атрибута color текущего экземпляра
геттеры и сеттеры в ООП python

По идее на этом этапе надо переписать все методы нашего класса, чтобы в них использовались геттеры и сеттеры вместо прямых обращений к атрибутам.
=)

Наследование

Идея в том, чтобы хранить в классе только необходимые для него объекты и атрибуты.
И структурировать, объединить в иерархию объекты.
Обычно это иллюстрируется на примерах животных: кошечки, собачки, кролики и т.п.
Есть класс животные, он имеет
  • атрибуты: число ног, имя, возраст.
  • методы: геттеры и сеттеры

Теперь создадим класс кошки. Кошки - тоже животные. Поэтому все атрибуты и методы класса животные им тоже присущи. Но кроме этого, у них могут быть свои:
  • атрибуты: имя и т.п.
  • методы: "говорение" (мяу) и т.п.

Чтобы не дублировать код, класс кошки наследуется от класса животные.

Наследник получает все методы и атрибуты родителя, а так же может некоторые из них переопределить (например, если мы сделаем класс "птицы" наследником класса "животные", то, вероятно, ограничение на число ног изменится с 4 до 2), а так же задать собственные атрибуты и методы.

Давайте в нашем примере представим, что наш класс точек - это объекты на географической карте. Точка может использоваться для оформления (например, прокладки маршрута из точек).
А новый класс будет представлять из себя географический маркер: банкомат, достопримечательность, организацию или что-то иное.
Новый класс будет наследником обычной точки, но мы чуть расширим конструктор, чтобы иметь больше атрибутов:
Пример простого наследования в python с переопределением конструктора
Как видим, наш класс получил возможность использовать сеттер родительского класса, да и стандартная функция __str__ тоже наследуется.

Давайте зададим нашему классу новый метод __str__, чтобы при выводе на печать понимать что это не просто точка, именно маркер. И внутри будем использовать метод __str__ родительского класса:
Пример простого наследования в python с переопределением методов
Т.е. вы всегда можете обратиться к родительским методам у экземпляров дочерних классов, если это необходимо.

Давайте создадим ещё пару классов.

1 будет идентичен предыдущим - это будет класс иконок с геттером и сеттером:
Пример создания класса, который станет в будущем "примесью" при наследовании
Такие классы ещё иногда называются "примесью", дальше станет понятно почему.

А вот 2 будет интереснее. Это будет класс организации. И мы не станем записывать для него ни атрибутов, ни классов. Но унаследуем его от 2 родителей:
Пример множественного наследования в python
Удивительно, но это работает.
При этом:
  • наследник получает все методы и атрибуты обоих родителей.
  • если у нескольких родителей есть одноимённые методы, то автоматически наследник получит методы того, кто раньше в списке (в нашем случае раньше был гео маркер, поэтому конструктор был унаследован от него, а не от организации).

Наследование от нескольких родителей иногда считается сомнительной практикой, будьте с ним аккуратны.

Переменные класса

Мы уже говорили про атрибуты в качестве хранилища данных (мы даже создавали их внутри класса в самом начале).
Есть ещё одно интересное применение для них - переменная класса. Т.е переменные, которые хранятся в классе, а не в экземпляре.

Модифицируем конструктор организации (добавим заодно поддержку url). В классе создадим переменную tag с первоначальным значением равным 0. В конструкторе же запишем в переменную ID создаваемого экземпляра значение из tag, а tag после этого увеличим на 1:
Переменная класса - атрибут класса изменяется при создании каждого экземпляра для создания счётчика числа экземпляров класса
Таким образом мы получили:
  • в атрибуте ID каждого объекта хранится его уникальный порядковый номер
  • в переменной tag класса хранится число созданных объектов (тут надо быть осторожнее, так как возможно мы захотим уменьшать это число при удалении объекта).

Ну и не забываем, что каждый экземпляр имеет доступ к атрибутам класса помимо собственных, т.е. tag:
Доступ к атрибуту класса (переменной класса) есть и из экземпляров

Переиспользование методов

Мы уже говорили про наследование методов, мы даже использовали методы родительского класса.
Но можно наследовать часть родительского метода (и собирать из нескольких родительских 1 свой). Сократим наш код организации, унаследовав конструкторы гео маркера и иконки:
Частичное наследование методов в python OOP
Мы совместили 2 наследуемых метода и свой код.
И всё это работает.
=)


Полезные ссылки:


Автор

Задойный А.В.

Специалист по 1С-Битрикс

Автор интеграции шаблона на платформу 1С-Битрикс.
Техническая поддержка в РФ.

Python - Объектно Ориентированное Программирование (ООП)

<h1>Python - Объектно Ориентированное Программирование (ООП)</h1> <i> <p style="text-align: right;"> В данной статье даются основы ООП в питоне </p> </i> <h2>В python всё - объекты.</h2> <i>*Аудитория в шоке, особо нервные дамочки падают в обморок*</i><br> <ul> <li>Числа - объекты<br> </li> <li>Строки - объекты<br> </li> <li>Списки - объекты<br> </li> <li>Классы - объекты</li> <li>...<br> </li> </ul> <br> <b>Если говорить просто, то "объекты" - это некая структура.</b><br> <p style="text-align: right;"> <i>И было слово...</i> </p> Чтобы создавать объекты, используются "конструкторы" или классы.<br> <ul> <li><b>Класс</b> - это схема, описывающая нашу структуру, возможные внутри неё данные и присущие ей методы.</li> <li> <b>Метод</b> - это функция. Т.е. метод объекта - это функция, описанная внутри объекта, и присущая этому объекту. Метод - это функция, которая действует на объекты данного вида. Для удобства у разных видов объектов могут быть методы с одинаковыми именами, работающие по разному, но схожим образом.</li> <li> <b>Экземпляр</b> - это конкретный объект, созданный из класса.</li> </ul> <br> <h2>Рассмотрим пример</h2> Список:<br> <pre>L = [1, 2, 3, 4]</pre><br> <ul> <li>Список (List) - это класс объекта<br> </li> <li>Переменная L содержит экземпляр объекта (конкретно список [1, 2, 3, 4])<br> </li> <li>append(), sort() - методы объекта, т.е. функции, которые можно применить к его экземплярам.<br> </li> </ul> <br> Пока звучит не очень сложно.<br> Но по прежнему не понятно <b>зачем </b>это нужно. =)<br> <br> 1 что приходит на ум - абстракция. Мы не думаем о том как устроен объект, а думаем о том что мы можем с ним сделать.<br> Например, возьмём функцию dir() и передадим экземпляр объекта список в неё как аргумент:<br> <pre>dir(L)</pre><br> <img width="831" alt="методы списка (dir)" src="/upload/medialibrary/cf6/cf6febe85764cedbee326bbb2f8ef328.png" height="533" title="методы списка (dir)"><br> На выходе получим большой список. Большой список методов для списка.<br> У нас есть 2 пути:<br> <ul> <li>опробовать некоторые из них (возможно, пытаясь передать какие-то атрибуты)<br> </li> <li>найти их описание в google по имени<br> </li> </ul> <br> Но в любом случае мы узнаем о том, "что можно делать со списком", не изучая то как он устроен.<br> <br> При этом если мы создадим свою структуру данных, свой класс со своими объектами, то у них могут быть одноимённые методы (например, если они ведут себя аналогично).<br> <br> Например, у строк есть несколько методов, одноимённых методом списков:<br> <img width="528" alt="методы строки (dir)" src="/upload/medialibrary/09b/09ba5cc187418e6581faa5897f04aace.png" height="418" title="методы строки (dir)"><br> Метод index() является одним из них.<br> Рассмотрим как он работает для наших примеров:<br> <img width="486" alt="поиск по индексу для списка и строки" src="/upload/medialibrary/548/54881578f2e7939599f23a5ed5a361a0.jpg" height="146" title="поиск по индексу для списка и строки"><br> <br> <br> Легко заметить, что некоторые методы в списке имели по 2 подчёркивания с обеих сторон, например:<br> <pre>__len__</pre> Это "стандартный" метод. Фактически, когда мы используем функцию len(), например:<br> <img width="486" alt="определение длинны списка и строки (len)" src="/upload/medialibrary/646/646ac24ea8c023c3c475ca48b03fc4b6.jpg" height="72" title="определение длинны списка и строки (len)"><br> то на самом деле вызывается метод __len__ соответствующего объекта.<br> <br> <b>Почему?</b><br> Всё потому же - если мы будем внутри функции языка len описывать как вычислять длину любого объекта, то это будет очень много кода. Да и для новых классов объектов (например, numpy.array) эта функция не будет работать.<br> А так у каждого класса внутри будет краткое описание того как это работает.<br> <br> Так же это позволяет переопределить поведение некоторых операторов.<br> Например, что будет, если мы напишем:<br> <img width="481" alt="Что будет если сложить строку и список в python?" src="/upload/medialibrary/d94/d94701f0d20f328544ed7d25bdb62afa.jpg" height="39" title="Что будет если сложить строку и список в python?"><br> Фактически будет вызван метод add - s.__add__(L)<br> <br> В нашем примере мы получим ошибку:<br> <img width="487" alt="Если в python сложить строку и список, то получится ошибка =)" src="/upload/medialibrary/016/016525987f5d6fde97435519ca8276b3.png" height="197" title="Если в python сложить строку и список, то получится ошибка =)"><br> <br> Но некоторые классы объектов вполне могут принимать на вход "чужака" (не всякого конечно):<br> <img width="488" alt="а numpy array и список в python сложить можно (они должны быть одной длины и сложатся поэлементно)" src="/upload/medialibrary/3a8/3a8e43d64d4201d048904abe05ab39b0.jpg" height="104" title="а numpy array и список в python сложить можно (они должны быть одной длины и сложатся поэлементно)"><br> <br> А ещё когда мы пишем<br> <pre>print L</pre><br> , то на самом деле вызывается метод __str__, чтобы преобразовать список L в строку для печати.<br> <br> Итак, <br> <ul> <li><b>класс</b> - это инструкция, по которой собирается конкретный объект<br> </li> <li><b>экземпляр</b> - это конкретный объект<br> </li> <li>а <b>метод </b>- это функция, присущая конкретному классу объектов.<br> </li> </ul> <br> <br> <b>И это всё?</b><br> <br> Нет. =)<br> <br> Когда мы говорим, что есть 2 экземпляра объектов список:<br> <ul> <li>[1, 2, 3, 4]<br> </li> <li>[1, 2, 3]<br> </li> </ul> <br> то очевидно, что они содержат разные данные.<br> Т.е. внутри объектов помимо методов есть данные, и хранятся они в атрибутах.<br> <br> Примером атрибута может быть shape для numpy:<br> <img width="487" alt="атрибут shape для numpy array" src="/upload/medialibrary/cea/cea90c9a47f5280d04b1330892f92aa9.jpg" height="62" title="атрибут shape для numpy array"><br> Мы просто создали вектор, передав в него данные. И при этом вычислился его размер.<br> Логично, для больших матриц в векторов проще хранить внутри 1 переменную с натуральным числом, чем каждый раз проходить по всем данным и тратить время для вычислений.<br> <br> <br> <h2>Давайте попрактикуемся</h2> Создадим пустой класс, который ничего не делает и ничего не хранит:<br> <img width="488" alt="python - пример пустого класса" src="/upload/medialibrary/10d/10d650640f08a45320dad2ca4eb4559e.png" height="173" title="python - пример пустого класса"><br> <br> Теперь создадим 1й метод у нашего класса.<br> Для этого создадим функцию, внутри метода. Она должна принимать по крайней мере 1 аргумент - self - это экземпляр того объекта, методов которого функция будет.<br> <br> <img width="486" alt="python - пример класса с 1 методом, выводящим на печать строку" src="/upload/medialibrary/a23/a23e556c69dac0fb884e59ac174e6b97.png" height="197" title="python - пример класса с 1 методом, выводящим на печать строку"><br> <br> Если мы хотим передавать в метод какие-то параметры, то просто зададим их после self, как и обычные параметры функции:<br> <img width="486" alt="Python - пример класса с простым методом, принимающим 1 аргумент" src="/upload/medialibrary/508/508d118e8eac4f0722b9837ca46228fb.png" height="253" title="Python - пример класса с простым методом, принимающим 1 аргумент"><br> <br> Это всё здорово, но пока особой разницы между методом и функцией нет.<br> Можно конечно рассматривать класс как некий контейнер для централизованного хранения функций.<br> Но обычно всё же класс подразумевает ещё и хранение данных в атрибутах.<br> <br> Синтаксис для атрибутов аналогичен синтаксису методов (только после атрибута не надо ставить круглые скобки):<br> <ul> <li>obj.method()<br> </li> <li>obj.atr<br> </li> </ul> <br> Давайте создадим атрибут у экземпляра объекта:<br> <img width="483" alt="создаём атрибут у уже существующего экземпляра объекта в python" src="/upload/medialibrary/ba0/ba095f1321c829e0ad74c876f8158ffe.jpg" height="107" title="создаём атрибут у уже существующего экземпляра объекта в python"><br> <br> Вполне очевидно, что если я создам 2й экземпляр этого же класса, и присвою уже в нём атрибуту с таким же именем какое-то значение, то у разных экземпляров в одноимённом атрибуте будут разные значения:<br> <img width="488" alt="добавляем 2 разных атрибута в 2 разных экземпляра одного объекта в python" src="/upload/medialibrary/0ad/0add110b281060ea171475e7a284e626.jpg" height="130" title="добавляем 2 разных атрибута в 2 разных экземпляра одного объекта в python"><br> <br> Так же я могу определить переменную внутри класса и она станет атрибутов всех экземпляров этого класса.<br> Естественно, это не отменит возможность принудительного переопределения соответствующего атрибута у какого-то экземпляра:<br> <img width="487" alt="создаём атрибут в классе python" src="/upload/medialibrary/e0e/e0e50f40038a3b01c5bb8307df7ab327.png" height="390" title="создаём атрибут в классе python"><br> Для экземпляров B и C значение атрибута равно значению по-умолчанию (списку), а для экземпляров E и A переопределено (на число и строку).<br> <br> Пока пользоваться классами не очень удобно:<br> <ul> <li>надо определить класс<br> </li> <li>создать объект<br> </li> <li>обратиться к каждому из атрибутов, записав туда кастомные данные<br> </li> <li>обратиться к каждому объекту, передав туда кастомные аргументы<br> </li> </ul> <br> Однако, процесс можно усовершенствовать в помощью конструктора (<i>на самом деле инициализатора, но мы в этой статье не будем углубляться в разницу</i>). Конструктор вызывается автоматически при создании экземпляра.<br> <br> Для этого служит метод __init__().<br> <br> Перепишем наш класс и убедимся, что его работа не изменилась:<br> <img width="495" alt="добавляем создание атрибута в инициализатор (конструктор) простого класса Python" src="/upload/medialibrary/db1/db1ccc4ba5f0ac61fcc1cdcf96e98f51.png" height="412" title="добавляем создание атрибута в инициализатор (конструктор) простого класса Python"><br> <b>Важно:</b><br> <ul> <li>метод __init__(), как и другие методы класса должен принимать как минимум 1 аргумент self<br> </li> <li>к аргументам объекта можно обращаться из метода как аргументам self (например, self.arg)<br> </li> <li>Если аргумент создаётся конструктором __init__, то его не нужно описывать в классе как отдельную переменную<br> </li> </ul> <br> Если для конструктора __init__ мы зададим какие-то аргументы кроме self, то их можно будет передать в конструктор при создании экземпляра объекта, и сразу записать в аргументы, если необходимо.<br> <br> <b>Давайте создадим чуть более осмысленный класс.</b><br> Пусть это будет класс "Точка". Точка у нас будет характеризоваться 2 координатами (x, y).<br> И сразу зададим метод для нашей точки, вычисляющий расстояние от неё до другой точки (для этого воспользуемся теоремой Пифагора):<br> <img width="600" alt="Класс создания точки на плоскости с методом вычисления расстояния между точек" src="/upload/medialibrary/bb6/bb6fa331e0461fa905a9227405f05190.png" height="262" title="Класс создания точки на плоскости с методом вычисления расстояния между точек"><br> <br> Здесь метод dist принимает 2 аргумента: экземпляр текущего объекта и ещё одного, между которыми необходимо найти расстояние.<br> Поскольку метод применяется к текущему экземпляру, то при вызове метода в скобках я указываю только 2ю точку).<br> <br> Если я хочу передавать в мой метод аргументы привычным образом (как аргументы в скобках по порядку), то мне необходимо указать полный путь до метода. Он начнётся с имени класса:<br> <img width="596" alt="вызов метода из класса Python напрямую с передачей 2 объектов в качестве аргументов" src="/upload/medialibrary/f79/f79683f7f7deb190429b899cef394130.jpg" height="63" title="вызов метода из класса Python напрямую с передачей 2 объектов в качестве аргументов"><br> Обратите внимание, что 2й способ обращения позволяет использовать классы как хранилище функций даже для стандартных типов данных.<br> <br> Вот пример такого "хранилища":<br> <img width="594" alt="Класс как хранилище функций - простой пример" src="/upload/medialibrary/432/43288a3971eabb5f193332a8417d7528.png" height="180" title="Класс как хранилище функций - простой пример"><br> <br> Заметим, что если мы попытаемся напечатать экземпляр объекта (а не его атрибуты, как раньше), то ничего хорошего не получим:<br> <img width="599" alt="Вывод на печать экземпляра объекта без метода __str__" src="/upload/medialibrary/d77/d77a6a91b87895b5a2e28d31ab9537c1.jpg" height="70" title="Вывод на печать экземпляра объекта без метода __str__"><br> <br> Мы можем понять экземпляром какого класса объект является и на какую область памяти смотрит указатель, но в работе это довольно бесполезно.<br> Согласитесь, печатая экземпляр списка мы получаем список - это удобно.<br> Воспользуемся стандартным методом __str__, который вызывается при его печати с помощью print:<br> <img width="604" alt="Добавление метода __str__ для корректной печати экземпляров объекта в python" src="/upload/medialibrary/37c/37cc5f3314b46d4910be075305e99cdf.png" height="343" title="Добавление метода __str__ для корректной печати экземпляров объекта в python"><br> <span style="color: #ee1d24; background-color: #ffffff;">Интересный эффект, который можно заметить - после пересоздания класса ранее созданные объекты не меняют своего поведения.</span><br> <br> Дело в том, что класс - это тоже объект.<br> <br> С точки зрения python мы создали 2 объекта. И на базе 1-ого создали несколько экземпляров.<br> Поэтому чтобы новые методы добавились у объектов, объекты придётся пересоздать.<br> <br> <br> Давайте заведём ещё 1 экземпляр объекта с точно такими же координатами, что и первый.<br> В бытовом понимании 2 такие точки "равны". Но что будет, если мы их сравним?<br> <img width="474" alt="Сравнение двух экземпляров одного объекта с идентичными атрибутами в python" src="/upload/medialibrary/ff9/ff96a611e27fefd45ddecfc1dca1e7e1.png" height="199" title="Сравнение двух экземпляров одного объекта с идентичными атрибутами в python"><br> С точки зрения python это 2 разных объекта, а потому они НЕ равны.<br> <br> Чтобы это исправить можно написать стандартный метод __eq__:<br> <img width="619" alt="Метод для сравнения двух экземпляров одного класса в python" src="/upload/medialibrary/848/84833775be224fa54857ce81b4a8cef7.png" height="427" title="Метод для сравнения двух экземпляров одного класса в python"><br> Замечу, что если мы заведём по новому атрибуту у наших точек (например, цвету), то операция сравнения их учитывать не будет:<br> <br> <img width="490" alt="метод класса учитывает не все атрибуты при сравнении в python (2 экземпляра имеют разные значения одного атрибута, но код считает их равными друг другу)" src="/upload/medialibrary/46a/46a7da771224f2e167d025c277eef3e8.jpg" height="126" title="метод класса учитывает не все атрибуты при сравнении в python (2 экземпляра имеют разные значения одного атрибута, но код считает их равными друг другу)"><br> <h3>Геттеры и Сеттеры</h3> Идея геттеров и сеттеров заключается в том, что передавать в экземпляр класса значения атрибутов явно - довольно опасная идея.<br> Т.к. питон язык с динамический типизацией, то мы можем передать любой тип данных в переменную с одним и тем же именем внутри разных экземпляров (мы так и делали раньше и считали это даже преимуществом)<br> <br> Но иногда при добавлении атрибута имеет смысл провести валидацию. Или иметь метод который не перезатирает значение, а добавляет (примером такого метода является append для списков).<br> <br> Добавим в наш класс 2 новых метода:<br> <ul> <li><b>сеттер </b>- setColor - проверяет, что передаётся строка и записывает её в атрибут color экземпляра. Если передан другой тип данных, возвращает ошибку.<br> </li> <li><b>геттер </b>- getColor - возвращает значение атрибута color текущего экземпляра<br> </li> </ul> <img width="610" alt="геттеры и сеттеры в ООП python" src="/upload/medialibrary/af5/af52731e94b583424ee6dd43bfd8c64b.png" height="959" title="геттеры и сеттеры в ООП python"><br> <br> По идее на этом этапе надо переписать все методы нашего класса, чтобы в них использовались геттеры и сеттеры вместо прямых обращений к атрибутам.<br> =)<br> <br> <h3>Наследование</h3> Идея в том, чтобы хранить в классе только необходимые для него объекты и атрибуты.<br> И структурировать, объединить в иерархию объекты.<br> Обычно это иллюстрируется на примерах животных: кошечки, собачки, кролики и т.п.<br> Есть класс животные, он имеет<br> <ul> <li>атрибуты: число ног, имя, возраст.<br> </li> <li>методы: геттеры и сеттеры<br> </li> </ul> <br> Теперь создадим класс кошки. Кошки - тоже животные. Поэтому все атрибуты и методы класса животные им тоже присущи. Но кроме этого, у них могут быть свои:<br> <ul> <li>атрибуты: имя и т.п.<br> </li> <li>методы: "говорение" (мяу) и т.п.<br> </li> </ul> <br> Чтобы не дублировать код, класс кошки наследуется от класса животные.<br> <br> Наследник получает все методы и атрибуты родителя, а так же может некоторые из них переопределить (например, если мы сделаем класс "птицы" наследником класса "животные", то, вероятно, ограничение на число ног изменится с 4 до 2), а так же задать собственные атрибуты и методы.<br> <br> Давайте в нашем примере представим, что наш класс точек - это объекты на географической карте. Точка может использоваться для оформления (например, прокладки маршрута из точек).<br> А новый класс будет представлять из себя географический маркер: банкомат, достопримечательность, организацию или что-то иное.<br> Новый класс будет наследником обычной точки, но мы чуть расширим конструктор, чтобы иметь больше атрибутов:<br> <img width="492" alt="Пример простого наследования в python с переопределением конструктора" src="/upload/medialibrary/d26/d26fbb31add2899a30b64883e2d79547.png" height="253" title="Пример простого наследования в python с переопределением конструктора"><br> Как видим, наш класс получил возможность использовать сеттер родительского класса, да и стандартная функция __str__ тоже наследуется.<br> <br> Давайте зададим нашему классу новый метод __str__, чтобы при выводе на печать понимать что это не просто точка, именно маркер. И внутри будем использовать метод __str__ родительского класса:<br> <img width="583" alt="Пример простого наследования в python с переопределением методов" src="/upload/medialibrary/32d/32d0b39d9e6fbbc5c47825d0ca3d27db.png" height="298" title="Пример простого наследования в python с переопределением методов"><br> Т.е. вы всегда можете обратиться к родительским методам у экземпляров дочерних классов, если это необходимо.<br> <br> Давайте создадим ещё пару классов.<br> <br> 1 будет идентичен предыдущим - это будет класс иконок с геттером и сеттером:<br> <img width="578" alt="Пример создания класса, который станет в будущем &quot;примесью&quot; при наследовании" src="/upload/medialibrary/671/671953621fd6851edbe7ad124d06c1e1.png" height="230" title="Пример создания класса, который станет в будущем &quot;примесью&quot; при наследовании"><br> <i>Такие классы ещё иногда называются "примесью", дальше станет понятно почему.</i><br> <br> А вот 2 будет интереснее. Это будет класс организации. И мы не станем записывать для него ни атрибутов, ни классов. Но унаследуем его от 2 родителей:<br> <img width="577" alt="Пример множественного наследования в python" src="/upload/medialibrary/d5a/d5a4b298594b6c4dc3579225de1ad010.png" height="273" title="Пример множественного наследования в python"><br> <b>Удивительно, но это работает.</b><br> При этом:<br> <ul> <li>наследник получает все методы и атрибуты обоих родителей.<br> </li> <li>если у нескольких родителей есть одноимённые методы, то автоматически наследник получит методы того, кто раньше в списке (в нашем случае раньше был гео маркер, поэтому конструктор был унаследован от него, а не от организации).</li> </ul> <br> <u><i>Наследование от нескольких родителей иногда считается сомнительной практикой, будьте с ним аккуратны.</i></u><br> <br> <h3>Переменные класса</h3> Мы уже говорили про атрибуты в качестве хранилища данных (мы даже создавали их внутри класса в самом начале).<br> Есть ещё одно интересное применение для них - переменная класса. Т.е переменные, которые хранятся в классе, а не в экземпляре.<br> <br> Модифицируем конструктор организации (добавим заодно поддержку url). В классе создадим переменную tag с первоначальным значением равным 0. В конструкторе же запишем в переменную ID создаваемого экземпляра значение из tag, а tag после этого увеличим на 1:<br> <img width="583" alt="Переменная класса - атрибут класса изменяется при создании каждого экземпляра для создания счётчика числа экземпляров класса" src="/upload/medialibrary/f9f/f9fa146df5c175dbf8b7edf6ea576118.png" height="478" title="Переменная класса - атрибут класса изменяется при создании каждого экземпляра для создания счётчика числа экземпляров класса"><br> Таким образом мы получили:<br> <ul> <li>в атрибуте ID каждого объекта хранится его уникальный порядковый номер<br> </li> <li>в переменной tag класса хранится число созданных объектов (тут надо быть осторожнее, так как возможно мы захотим уменьшать это число при удалении объекта).<br> </li> </ul> <br> Ну и не забываем, что каждый экземпляр имеет доступ к атрибутам класса помимо собственных, т.е. tag:<br> <img width="582" alt="Доступ к атрибуту класса (переменной класса) есть и из экземпляров" src="/upload/medialibrary/a0b/a0b18b34e0b7bdc01efe6db52827232f.jpg" height="76" title="Доступ к атрибуту класса (переменной класса) есть и из экземпляров"><br> <h3>Переиспользование методов</h3> Мы уже говорили про наследование методов, мы даже использовали методы родительского класса.<br> Но можно наследовать часть родительского метода (и собирать из нескольких родительских 1 свой). Сократим наш код организации, унаследовав конструкторы гео маркера и иконки:<br> <img width="606" alt="Частичное наследование методов в python OOP" src="/upload/medialibrary/1d4/1d40d273b3cee13ad2d5bca39ad38ace.png" height="405" title="Частичное наследование методов в python OOP"><br> Мы совместили 2 наследуемых метода и свой код.<br> И всё это работает.<br> =)<br> <br> <br> <div> <b>Полезные ссылки:</b> </div> <!--noindex--> <ul> <li> <a href="https://www.ibm.com/developerworks/ru/library/l-python_part_6/index.html" rel="nofollow" target="_blank">https://www.ibm.com/developerworks/ru/library/l-python_part_6/index.html</a><br> </li> <li><a rel="nofollow" href="https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BD%D0%B0_Python" target="_blank">https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BD%D0%B0_Python</a><br> </li> <li><a rel="nofollow" href="https://pythonworld.ru/osnovy/obektno-orientirovannoe-programmirovanie-obshhee-predstavlenie.html" target="_blank">https://pythonworld.ru/osnovy/obektno-orientirovannoe-programmirovanie-obshhee-predstavlenie.html</a><br> </li> <li><a rel="nofollow" href="https://www.codecademy.com/courses/learn-python/lessons/introduction-to-classes/exercises/why-use-classes" target="_blank">https://www.codecademy.com/courses/learn-python/lessons/introduction-to-classes/exercises/why-use-classes</a><br> </li> <li><b>MIT6.00.1x про ООП:</b></li> <ul> <li><a rel="nofollow" href="https://youtu.be/nQXFr9Ma2lk" target="_blank">https://youtu.be/nQXFr9Ma2lk</a></li> <li><a rel="nofollow" href="https://youtu.be/2Dy-JQZwz5A" target="_blank">https://youtu.be/2Dy-JQZwz5A</a><br> </li> <li><a rel="nofollow" href="https://youtu.be/cx7m9hDG_OE" target="_blank">https://youtu.be/cx7m9hDG_OE</a><br> </li> <li><a rel="nofollow" href="https://youtu.be/6wMNPuoudrA" target="_blank">https://youtu.be/6wMNPuoudrA</a><br> </li> <li><a rel="nofollow" href="https://youtu.be/Vfw6XoUOOvc" target="_blank">https://youtu.be/Vfw6XoUOOvc</a><br> </li> <li><a rel="nofollow" href="https://youtu.be/08V2ukhKe1k" target="_blank">https://youtu.be/08V2ukhKe1k</a><br> </li> <li><a rel="nofollow" href="https://youtu.be/6CbZiKX5mRw" target="_blank">https://youtu.be/6CbZiKX5mRw</a><br> </li> <li><a rel="nofollow" href="https://youtu.be/Y4sR0fUq6bY" target="_blank">https://youtu.be/Y4sR0fUq6bY</a><br> </li> <li><a rel="nofollow" href="https://youtu.be/LnnEPJebkbU" target="_blank">https://youtu.be/LnnEPJebkbU</a><br> </li> <li><a rel="nofollow" href="https://youtu.be/IxKLyEa4DsI" target="_blank">https://youtu.be/IxKLyEa4DsI</a></li> </ul> </ul> <!--/noindex--> <br>

Возврат к списку

Яндекс.Метрика