ООП
self
Методы класса имеют одно отличие от обычных функций: они должны иметь дополнительно имя, добавляемое к началу списка параметров. Однако, при вызове метода никакого значения этому параметру присваивать не нужно – его укажет Python. Эта переменная указывает на сам объект экземпляра класса, и по традиции она называется self
.
Хотя этому параметру можно дать любое имя, настоятельно рекомендуется использовать только имя self
; использование любого другого имени не приветствуется. Есть много достоинств использования стандартного имени: во-первых, любой человек, просматривающий вашу программу, легко узнает его; во-вторых, некоторые специализированные Интегрированные среды разработки (IDE) изначально рассчитаны на использование self
.
Замечание для программистов на C++, Java и C#
self
в Python эквивалентно указателю this
в C++ и ссылке this
в Java и C#.
Вы, должно быть, удивляетесь, как Python присваивает значение self
и почему вам не нужно указывать это значение самостоятельно. Поясним это на примере. Предположим, у нас есть класс с именем MyClass
и экземпляр этого класса с именем myobject
. При вызове метода этого объекта, например, “myobject.method(arg1, arg2)
”, Python автоматически превращает это в “MyClass.method(myobject, arg1, arg2)
” – в этом и состоит смысл self
.
Это также означает, что если какой-либо метод не принимает аргументов, у него всё равно будет один аргумент – self
.
Классы
Простейший класс показан в следующем примере (сохраните как simplestclass.py
).
Вывод:
Как это работает:
Мы создаём новый класс при помощи оператора
class
и имени класса. За этим следует блок выражений, формирующих тело класса. В данном случае блок у нас пуст, на что указывает операторpass
.Далее мы создаём объект-экземпляр класса, записывая имя класса со скобками. Для проверки мы выясняем тип переменной, просто выводя её на экран. Так мы видим, что у нас есть экземпляр класса
Person
в модуле__main__
.Обратите внимание, что выводится также и адрес в памяти компьютера, где хранится ваш объект. На вашем компьютере адрес будет другим, так как Python хранит объекты там, где имеется свободное место.
Методы объектов
Итак, мы выяснили что классы/объекты могут иметь методы, представляющие собой функции, за исключением дополнительной переменной self
. А теперь давайте рассмотрим пример (сохраните как method.py
).
Вывод:
Как это работает:
Здесь мы видим
self
в действии. Обратите внимание, что методsayHi
не принимает параметров, но тем не менее, имеетself
в определении функции.
Метод __init__
__init__
Существует много методов, играющих специальную роль в классах Python. Сейчас мы увидим значительность метода __init__
.
Метод __init__
запускается, как только объект класса реализуется. Этот метод полезен для осуществления разного рода инициализации, необходимой для данного объекта. Обратите внимание на двойные подчёркивания в начале и в конце имени.
Пример: (сохраните как oop_init.py
)
Вывод:
Как это работает:
Здесь мы определяем метод
__init__
так, чтобы он принимал параметрname
(наряду с обычнымself
). Далее мы создаём новое поле с именемname
. Обратите внимание, что это две разные переменные, даже несмотря на то, что они обе названыname
. Это не проблема, так как точка в выраженииself.name
обозначает, что существует нечто с именем “name”, являющееся частью объекта “self”, и другоеname
– локальная переменная. Поскольку мы в явном виде указываем, к которому имени мы обращаемся, путаницы не возникнет.Для создания нового экземпляра
p
классаPerson
мы указываем имя класса, после которого – аргументы в скобках:p = Person('Swaroop')
.Метод
__init__
мы при этом не вызываем явным образом. В этом и заключается специальная роль данного метода.После этого мы получаем возможность использовать поле
self.name
в наших методах, что и продемонстрировано в методеsay_hi
.
Переменные класса и объекта
Функциональную часть классов и объектов (т.е. методы) мы обсудили, теперь давайте ознакомимся с частью данных. Данные, т.е. поля, являются не чем иным, как обычными переменными, заключёнными в пространствах имён классов и объектов. Это означает, что их имена действительны только в контексте этих классов или объектов. Отсюда и название “пространство имён”.
Существует два типа полей: переменные класса и переменные объекта, которые различаются в зависимости от того, принадлежит ли переменная классу или объекту соответственно.
Переменные класса разделяемы – доступ к ним могут получать все экземпляры этого класса. Переменная класса существует только одна, поэтому когда любой из объектов изменяет переменную класса, это изменение отразится и во всех остальных экземплярах того же класса.
Переменные объекта принадлежат каждому отдельному экземпляру класса. В этом случае у каждого объекта есть своя собственная копия поля, т.е. не разделяемая и никоим образом не связанная с другими такими же полями в других экземплярах. Это легко понять на примере (сохраните как objvar.py
):
Вывод:
Как это работает:
Это длинный пример, но он помогает продемонстрировать природу переменных класса и объекта. Здесь
population
принадлежит классуRobot
, и поэтому является переменной класса. Переменнаяname
принадлежит объекту (ей присваивается значение при помощиself
), и поэтому является переменной объекта.Таким образом, мы обращаемся к переменной класса
population
какRobot.population
, а неself.population
. К переменной же объектаname
во всех методах этого объекта мы обращаемся при помощи обозначенияself.name
. Помните об этой простой разнице между переменными класса и объекта. Также имейте в виду, что переменная объекта с тем же именем, что и переменная класса, сделает недоступной (“спрячет”) переменную класса!Метод
howMany
принадлежит классу, а не объекту. Это означает, что мы можем определить его какclassmethod
илиstaticmethod
, в зависимости от того, нужно ли нам знать, в каком классе мы находимся. Поскольку нам не нужна такая информация, мы воспользуемсяstaticmethod
.
Мы могли достичь того же самого, используя декораторы :
Декораторы можно считать неким упрощённым способом вызова явного оператора, как мы видели в этом примере.
Пронаблюдайте, как метод __init__
используется для инициализации экземпляра Robot
с именем. В этом методе мы увеличиваем счётчик population
на 1, так как добавляем ещё одного робота. Также заметьте, что значения self.name
для каждого объекта свои, что указывает на природу переменных объекта.
Помните, что к переменным и методам самого объекта нужно обращаться, пользуясь только self
. Это называется доступом к атрибутам.
В этом примере мы также наблюдали применение строк документации для классов, равно как и для методов. Во время выполнения мы можем обращаться к строке документации класса при помощи “Robot.__doc__
”, а к строке документации метода – при помощи “Robot.sayHi.__doc__
”.
Наряду с методом __init__
, существует и другой специальный метод __del__
, который вызывается тогда, когда объект собирается умереть, т.е. когда он больше не используется, и занимаемая им память возвращается операционной системе для другого использования. В этом методе мы просто уменьшаем счётчик Robot.population
на 1.
Метод __del__
запускается лишь тогда, когда объект перестаёт использоваться, а поэтому заранее неизвестно, когда именно этот момент наступит. Чтобы увидеть его в действии явно, придётся воспользоваться оператором del
, что мы и сделали выше.
Примечание для программистов на C++/Java/C#
В Python все члены класса (включая данные) являются публичными (public), а все методы – виртуальными (virtual).
Исключение: Если имя переменной начинается с двойного подчёркивания, как, например, __privatevar
, Python делает эту переменную приватной (private). Поэтому принято имя любой переменной, которая должна использоваться только внутри класса или объекта, начинать с подчёркивания; все же остальные имена являются публичными, и могут использоваться в других классах/объектах. Помните, что это лишь традиция, и Python вовсе не обязывает делать именно так (кроме двойного подчёркивания).
Наследование
Одно из главных достоинств объектно-ориентированного программирования заключается в многократном использовании одного и того же кода, и один из способов этого достичь – при помощи механизма наследования. Легче всего представить себе наследование в виде отношения между классами как тип и подтип.
Представим, что нам нужно написать программу, которая отслеживает информацию о преподавателях и студентах в колледже. У них есть некоторые общие характеристики: имя, возраст и адрес. Есть также и специфические характеристики, такие как зарплата, курсы и отпуск для преподавателей, а также оценки и оплата за обучение для студентов.
Можно создать для них независимые классы и работать с ними, но тогда добавление какой-либо новой общей характеристики потребует добавления её к каждому из этих независимых классов в отдельности, что делает программу неповоротливой.
Лучше создать общий класс с именем SchoolMember
, а затем сделать так, чтобы классы преподавателя и студента наследовали этот класс, т.е. чтобы они стали подтипами этого типа (класса), после чего добавить любые специфические характеристики к этим подтипам.
У такого подхода есть множество достоинств. Если мы добавим/изменим какую-либо функциональность в SchoolMember
, это автоматически отобразится и во всех подтипах. Например, мы можем добавить новое поле удостоверения для преподавателей и студентов, просто добавив его к классу SchoolMember
. С другой стороны, изменения в подтипах никак не влияют на другие подтипы. Ещё одно достоинство состоит в том, что обращаться к объекту преподавателя или студента можно как к объекту SchoolMember
, что может быть полезно в ряде случаев, например, для подсчёта количества человек в школе. Когда подтип может быть подставлен в любом месте, где ожидается родительский тип, т.е. объект считается экземпляром родительского класса, это называется полиморфизмом.
Заметьте также, что код родительского класса используется многократно, и нет необходимости копировать его во все классы, как пришлось бы в случае использования независимых классов.
Класс SchoolMember
в этой ситуации называют базовым классом или надклассом. Классы Teacher
и Student
называют производными классами или подклассами.
Рассмотрим теперь этот пример в виде программы (сохраните как inherit.py
).
Вывод:
Как это работает:
Чтобы воспользоваться наследованием, при определении класса мы указываем имена его базовых классов в виде кортежа, следующего сразу за его именем. Далее мы видим, что метод
__init__
базового класса вызывается явно при помощи переменнойself
, чтобы инициализировать часть объекта, относящуюся к базовому классу. Это очень важно запомнить: поскольку мы определяем метод__init__
в подклассахTeacher
иStudent
, Python не вызывает конструктор базового классаSchoolMember
автоматически – его необходимо вызывать самостоятельно в явном виде.Напротив, если мы не определим метод
__init__
в подклассе, Python вызовет конструктор базового класса автоматически.Здесь же мы видим, как можно вызывать методы базового класса, предваряя запись имени метода именем класса, а затем передавая переменную
self
вместе с другими аргументами.Обратите внимание, что при вызове метода
tell
из классаSchoolMember
экземплярыTeacher
илиStudent
можно использовать как экземплярыSchoolMember
.Заметьте также, что вызывается метод
tell
из подкласса, а не методtell
из классаSchoolMember
. Это можно понять следующим образом: Python всегда начинает поиск методов в самом классе, что он и делает в данном случае. Если же он не находит метода, он начинает искать методы, принадлежащие базовым классам по очереди, в порядке, в котором они перечислены в кортеже при определении класса.Замечание по терминологии: если при наследовании перечислено более одного класса, это называется множественным наследованием.
Параметр
end
используется в методеtell()
для того, чтобы новая строка начиналась через пробел после вызоваprint()
.
Метаклассы
В обширной теме объектно-ориентированного программирования существует ещё много всего, но мы лишь слегка коснёмся некоторых концепций, чтобы вы просто знали об их существовании.
Точно так же, как классы используются для создания объектов, можно использовать метаклассы для создания классов. Метаклассы существуют для изменения или добавления нового поведения в классы.
Давайте рассмотрим пример. Допустим, мы хотим быть уверены, что мы всегда создаём исключительно экземпляры подклассов класса SchoolMember
, и не создаём экземпляры самого класса SchoolMember
.
Для достижения этой цели мы можем использовать концепцию под названием “абстрактные базовые классы”. Это означает, что такой класс абстрактен, т.е. является лишь некой концепцией, не предназначенной для использования в качестве реального класса.
Мы можем объявить наш класс как абстрактный базовый класс при помощи встроенного метакласса по имени ABCMeta
.
Вывод:
Как это работает:
Мы можем объявить метод tell
класса SchoolMember
абстрактным, и таким образом автоматически запретим создавать экземпляры класса SchoolMember
.
Тем не менее, мы можем работать с экземплярами Teacher
и Student
так, как будто они экземпляры SchoolMember
, поскольку они являются подклассами.
Last updated