Объектно-ориентированное программирование на Python: классы, описание и особенности

В Python классы являются фундаментальным понятием. Это основа стандартной библиотеки, работы большинства популярных программ и самого языка. Если вы хотите стать больше, чем просто начинающим программистом, вы должны понимать суть и принцип работы с классами и объектами.

Что такое классы

Это базовый программный компонент ООП. В Python классы используются для реализации новых типов объектов и создаются с помощью специальной инструкции class. Внешне они напоминают стандартные встроенные виды данных, такие как числа или последовательности. Но у объектов класса есть существенное различие – поддержка наследования.

Объектно-ориентированное программирование в Python полностью базируется на иерархическом наследовании классов. Это универсальный способ адаптации и многократного использования кода. Но объектно-ориентированный подход не является обязательным. Python без проблем допускает исключительно процедурное и функциональное программирование.

Главная задача классов в Python – упаковка данных и исполняемого кода. Синтаксически они похожи на инструкции def. Подобно функциям, они создают свои пространства имен, которые можно неоднократно вызывать из любой части программы. Зачем же тогда они нужны? Классы – это более мощный и универсальный инструмент. Сильнее всего их потенциал раскрывается в момент создания новых объектов.

Важность классов и принцип наследования

У каждого нового объекта есть свое пространство имен, которое можно программировать, вводить переменные и создавать любые функции. А также есть атрибуты, унаследованные от класса: object.attribute. В этом заключается смысл ООП.

Благодаря наследованию, создается древо иерархии. На практике это выглядит следующим образом. Когда интерпретатор встречает выражение object.attribute, он начинает искать первое вхождение attribute в указанном class. Не обнаружив attribute, интерпретатор продолжает поиск во всех связанных классах, находящихся в дереве выше, по направлению слева направо.

В древо поиска входят:

  • суперклассы, которые находятся на самом верху иерархии и реализуют общее поведение;
  • подклассы – находятся ниже;
  • экземпляры – элементы программы с унаследованным поведением.

На рисунке изображено дерево классов Python. Из примера видно, что Class 2 и 3 – это суперклассы. В самом низу находятся два экземпляра Instance 1 и 2, в середине – подкласс Class 1. Если написать выражение Instance2.w, оно заставит интерпретатор искать значение атрибута .w в следующем порядке:

  1. Instance2;
  2. Class1;
  3. Class2;
  4. Class3.

Имя .w ,будет найдено в суперклассе Class3. На терминологии ООП – это означает, что Instance 2 «наследует» атрибут .w от Class3.

Обратите внимание, что экземпляры на рисунке наследуют только четыре атрибута: .w, .x, .y и .z:

  • Для экземпляров Instance1.x и Instance2.x атрибут .x будет найден в Class 1, где поиск остановится, потому что Class 1 находится в дереве ниже, чем Class 2.
  • Для Instance1.y и Instance2.y атрибут .y будет найден в Class 1, где поиск остановится, потому что это единственное место, где он появляется.
  • Для экземпляров Instance1.z и Instance2.z интерпретатор найдет .z в Class 2, потому что он располагается в дереве левее, чем Class3.
  • Для Instance2.name атрибут .name будет найден в Instance2 без поиска по дереву.

Предпоследний пункт является самым важным. Он демонстрирует, как Class 1 переопределяет атрибут .x, замещая версию .x суперкласса Class 2.

Объекты, экземпляры и методы

ООП оперирует двумя главными понятиями: классы и объекты. Классы создают новые типы, а объекты классов в Python являются их экземплярами. Например, все целочисленные переменные относятся к встроенному типу данных int. На языке ООП они являются экземплярами класса int.

Классы создаются инструкциями, а объекты с помощью вызовов. Они могут хранить данные и обладать своим функционалом или методами классов. В Python терминология играет важную роль. С ее помощью программисты отличают независимые функции от тех, что принадлежат классам. Переменные, относящиеся к объектам, называют полями.

Различают два вида полей в ООП. Первый – это переменные, принадлежащие целому классу, второй – переменные отдельных экземпляров. Поля и методы вместе являются атрибутами класса. В Python они записываются в блоке кода после ключевого слова class.

Методы и значение self

Методы – это функции с дополнительным именем self. Оно добавляется к началу списка параметров. При желании переменную можно назвать другим именем, но такая инициатива среди программистов не приветствуется. Self – это стандартное, легко узнаваемое в коде имя. Тем более на работу с ним рассчитаны некоторые среды разработки.

Чтобы лучше понять значение self в ООП, представим, что у нас есть класс с именем ClassA и методом methodA:

  • >>>class ClassA;
  • def methodA (self, аргумент1, аргумент2).

Объект objectA является экземпляром ClassA и вызов метода выглядит следующим образом:

  • >>>objectA.methodA(аргумент1, аргумент2).

Когда интерпретатор видит эту строчку, он ее автоматически преобразует следующим образом: ClassA.methodA(objectA, аргумент1, аргумент2). То есть экземпляр класса использует переменную self как ссылку на самого себя.

Как создавать переменные, методы и экземпляры классов

Предлагаем разобрать практический пример из интерактивной оболочки Python. Создание класса «ЭкспериментПервый» начинается с составной инструкции class:

  • >>>class ЭкспериментПервый:
  • def setinf(self, значение): #создаем метод первый с аргументами
  • self.data = значение
  • def display(self): #метод второй
  • print(self.data) #напечатать данные экземпляра.

После обязательного отступа следует блок с вложенными инструкциями def, в которых двум объектам функций присваиваются имена setinf и display. С их помощью создаются атрибуты ЭкспериментПервый.setinf и ЭкспериментПервый.display. Фактически любое имя, которому присваивается значение на верхнем уровне во вложенном блоке, становится атрибутом.

Чтобы увидеть, как работают методы, необходимо создать два экземпляра:

  • >>>x = ЭкспериментПервый () # Создаются два экземпляра;
  • >>>y = ЭкспериментПервый () # Каждый является отдельным пространством имен.

Изначально экземпляры не хранят никакой информации и абсолютно пусты. Но они связанны со своим классом:

  • >>>x.setinf(«Учим Питон») #Вызов метода, в котором self – это x.
  • >>>y.setinf(3.14) #Эквивалентно: ЭкспериментПервый.setinf(y, 3.14)

Если через имя экземпляров x, y обратиться к атрибуту .setinf объекта класса ЭкспериментПервый, то в результате поиска по дереву наследования интерпретатор возвращает значение атрибута класса.

  • >>>x.display() #У x и y свои значения self.data
  • Учим Питон
  • >>>y.display()
  • 3.14.

Перегрузка операторов

В языке Python классы могут перегружать операторы выражений. Такая возможность делает экземпляры похожими на встроенные типы данных. Процесс заключается в реализации методов со специальными именами, начинающимися и заканчивающимися двойным подчеркиванием.

Рассмотрим в действии __init__ и __sub__. Первый метод называют конструктором класса. В Python __init__ выполняет перегрузку операции создания экземпляров. Второй метод __sub__ реализует операцию вычитания.

  • >>>class Перегрузка: #создается новый класс
  • def __init__(self, start):
  • self.data = start
  • def __sub__(self, other): # экземпляр минус other
  • return Перегрузка(self.data - other) #Результатом будет новый экземпляр
  • >>>A = Перегрузка(10) #__init__(A, 10)
  • >>>B = A – 2 #__sub__(B, 2)
  • >>>B.data #B – это новый экземпляр класса Перегрузка
  • 8.

Подробнее о методе __init__

Метод __init__ используется чаще всего при работе с классами. Он незаменим для инициализации различных объектов. __init__ не нужно отдельно вызывать. При создании нового экземпляра метод автоматически получает аргументы, указанные в скобках.

С помощью методов перегрузки можно реализовать любые операции со встроенными типами данных. Большинство используются только при решении специальных задач, в которых необходимо, чтобы объекты имитировали поведение стандартных объектов.

Методы наследуются от суперклассов и не являются обязательными. На начальных этапах можно легко без них обойтись. Но для полного погружения в программирование и суть ООП нужен навык работы с операторами.

Метод __getitem__

Метод __getitem__ выполняет перегрузку доступа к элементу по индексу. Если он наследуется или присутствует в определении класса, то при каждой операции индексирования интерпретатор будет вызывать его автоматически. Например, когда экземпляр F появляется в выражении извлечения элемента по индексу, таком как F[i], интерпретатор Python вызывает метод __getitem__, передает объект F в первом аргументе и индекс, указанный в квадратных скобках, во втором.

Следующий класс «ПримерИндексации» возвращает квадрат значения индекса:

  • >>>class ПримерИндексации:
  • def __getitem__(self, index):
  • return index ** 2
  • >>>F = ПримерИндексации ()
  • >>>F[2] #Выражение F[i] вызывает F.__getitem__(i)
  • 4
  • >>>for i in range(5):
  • print(F[i], end=« ») # Вызывает __getitem__(F, i) в каждой итерации
  • 0 1 4 9 16

С помощью этого же метода можно выполнить операцию извлечения среза, к которой часто прибегают во время работы с последовательностями. При обработке списков стандартный синтаксис операции выглядит следующим образом:

  • >>>Список = [13, 6, «и», «с», 74,9]
  • >>>Список[2:4]
  • [«и», «с»]
  • >>>Список[1:]
  • [6, «и», «с», 74,9]
  • >>>Список[:-1]
  • [13, 6, «и», «с»]
  • >>>Список[::2]
  • [13, «и», 74,9]

Класс, реализующий метод __getitem__:

  • >>>class Индексатор:
  • мой_список = [13, 6, «и», «с», 74,9]
  • def __getitem__(self, индекс): #Вызывается при индексировании или извлечении среза
  • print(«getitem: », индекс)
  • return self.мой_список[индекс] #Выполняет индексирование или извлекает срез
  • >>>X = Индексатор()
  • >>>X[0] #При индексировании __getitem__ получает целое число
  • getitem: 0
  • 13
  • >>>X[2:4] # При извлечении среза __getitem__ получает объект среза
  • getitem: slice(2, 4, None)
  • [«и», «с»]

Обращения к атрибутам

Для получения ссылки на атрибут используется специальный метод __getattr__. Он вызывается с именем атрибута в виде строки в случаях обнаружения попытки получить ссылку на несуществующий или неопределенный атрибут. Когда интерпретатор может обнаружить искомый объект в дереве наследования, __getattr__.не вызывается.

Метод удобен для обобщенной обработки запросов к атрибутам:

  • >>>class Gone:
  • def __getattr__(self, atname):
  • if atname == «age»:
  • return 20
  • else:
  • raise AttributeError, atname
  • >>>D = Gone()
  • >>>D.age
  • 20
  • >>>D.name
  • AttributeError: name

У класса Gone и его экземпляра D своих атрибутов нет. Поэтому при обращении к D.age автоматически вызывается метод __getattr__. Сам экземпляр передается как self, а имя неопределенного «age» в строке atname. Класс возвращает результат обращения к имени D.age, несмотря на то, что данного атрибута у него нет.

Если классом не предусматривается обработка атрибута, метод __getattr__ вызывает встроенное исключение, и тем самым передает интерпретатору информацию о том, что имя в действительности является неопределенным. В данном случае попытка обратиться к имени D.name приводит к появлению ошибки.

Аналогичным образом работает метод перегрузки операторов __setattr__, перехватывая каждую попытку присвоить значение атрибуту. Если этот метод прописан в теле класса, выражение «self.атрибут = значение» будет преобразовано в вызов метода self.__setattr_(«атрибут», значение).

Мы описали только несколько из существующих методов перегрузки. Весь перечень находится в стандартном руководстве языка и включает гораздо больше имен.

Дополнительные возможности

ООП иногда используют для сложных и нестандартных задач. Благодаря наследованию классов в Python, поведение встроенных типов данных и их возможности поддаются расширению и адаптации.

Если вас не устраивает тот факт, что индексация в последовательностях начинается с нуля, вы можете это исправить с помощью инструкции class. Для этого нужно создать подкласс типа list с новыми именами всех типов и реализовать необходимые изменения. Также в ООП на языке Python существуют декораторы функций, статические методы и множество других сложных и специальных приемов.

Комментарии