09-06-2018 12:09

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

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

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

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

Что такое объекты типа str в PythonВам будет интересно:Что такое объекты типа str в Python

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

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

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

Как пройти рискометр инсульта?Вам будет интересно:Как пройти рискометр инсульта?

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

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

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

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

Работа с файловыми объектами: функция open, чтение и запись в файлы в PythonВам будет интересно:Работа с файловыми объектами: функция open, чтение и запись в файлы в Python

На рисунке изображено дерево классов 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 – это стандартное, легко узнаваемое в коде имя. Тем более на работу с ним рассчитаны некоторые среды разработки.

    Условные инструкции if/else в Python: синтаксис и применениеВам будет интересно:Условные инструкции if/else в Python: синтаксис и применение

    Чтобы лучше понять значение 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.
    " class="page-contents-link">

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

    В языке 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)
    • [«и», «с»]
    " class="page-contents-link">

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

    Для получения ссылки на атрибут используется специальный метод __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 существуют декораторы функций, статические методы и множество других сложных и специальных приемов.



    Источник