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


Опубликованно 17.01.2018 05:27

Объектно-ориентированное программирование на 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 в следующем порядке:Instance2;Class1;Class2;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 = startdef __sub__(self, other): # экземпляр минус otherreturn Перегрузка(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: 013>>>X[2:4] # При извлечении среза __getitem__ получает объект срезаgetitem: slice(2, 4, None)[«и», «с»]

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

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

Метод удобен для обобщенной обработки запросов к атрибутам:>>>class Gone:def __getattr__(self, atname):if atname == «age»:return 20else:raise AttributeError, atname>>>D = Gone()>>>D.age20>>>D.nameAttributeError: name

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

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

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

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

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

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



Категория: Техника