ООП на Python: концепции, принципы и примеры реализации

1
34362
Добавить в избранное

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

ООП на Python

Объектно-ориентированная парадигма имеет несколько принципов:

  • Данные структурируются в виде объектов, каждый из которых имеет определенный тип, то есть принадлежит к какому-либо классу.
  • Классы – результат формализации решаемой задачи, выделения главных ее аспектов.
  • Внутри объекта инкапсулируется логика работы с относящейся к нему информацией.
  • Объекты в программе взаимодействуют друг с другом, обмениваются запросами и ответами.
  • При этом объекты одного типа сходным образом отвечают на одни и те же запросы.
  • Объекты могут организовываться с более сложные структуры, например, включать другие объекты или наследовать от одного или нескольких объектов.

Если вам интересно, что думает об этой концепции сам ее создатель Алан Кэй, загляните сюда.

Язык Python – типичный представитель ООП-семейства, обладающий элегантной и мощной объектной моделью. В этом языке от объектов никуда не спрятаться (ведь даже числа являются ими), поэтому давайте разбираться, как это все реализовано.

Основы ООП на Python для начинающих

Классы

Создавать классы в Python очень просто:

Классы-родители перечисляются в скобках через запятую:

С реализацией наследования разберемся чуть позже.

Свойства классов устанавливаются с помощью простого присваивания:

Методы объявляются как простые функции:

Обратите внимание на первый аргумент – self – общепринятое имя для ссылки на объект, в контексте которого вызывается метод. Этот параметр обязателен и отличает метод класса от обычной функции.

Все пользовательские атрибуты сохраняются в атрибуте __dict__, который является словарем.

Экземпляры классов

Инстанцировать класс в Python тоже очень просто:

Можно создавать разные инстансы одного класса с заранее заданными параметрами с помощью инициализатора (специальный метод __init__). Для примера возьмем класс Point (точка пространства), объекты которого должны иметь определенные координаты:

Подробнее о других специальных методах жизненного цикла объектов поговорим чуть ниже.

Динамическое изменение

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

Кажется, этот класс совершенно бесполезен? Отнюдь. Классы в Python могут динамически изменяться после определения:

Статические и классовые методы

Для создания статических методов в Python предназначен декоратор @staticmethod. У них нет обязательных параметров-ссылок вроде self. Доступ к таким методам можно получить как из экземпляра класса, так и из самого  класса:

Еще есть так называемые методы классов. Они аналогичны методам экземпляров, но выполняются не в контексте объекта, а в контексте самого класса  (классы – это тоже объекты). Такие методы создаются с помощью декоратора @classmethod и требуют обязательную ссылку на класс (cls).

Статические и классовые методы доступны без инстанцирования.

Специальные методы

Жизненный цикл объекта

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

Это обсуждение на stackoverflow поможет лучше разобраться с инстанцированием классов.

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

В Python вы можете поучаствовать не только в создании объекта, но и в его удалении. Специально для этого предназначен метод-деструктор __del__.

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

Объект как функция

Объект класса может имитировать стандартную функцию, то есть при желании его можно «вызвать» с параметрами. За эту возможность отвечает специальный метод __call__:

Имитация контейнеров

Вы знакомы с функцией len(), которая умеет вычислять длину списков значений?

Но для объектов вашего пользовательского класса это не пройдет:

Этот код выдаст ошибку object of type 'Collection' has no len(). Интерпретатор просто не понимает, как ему посчитать длину collection.

Решить эту проблему поможет специальный метод __len__:

Можно работать с объектом как с коллекцией значений, определив для него интерфейс классического списка с помощью специальных методов:

  • __getItem__ – реализация синтаксиса obj[key], получение значения по ключу;
  • __setItem__ – установка значения для ключа;
  • __delItem__ – удаление значения;
  • __contains__ – проверка наличия значения.

Имитация числовых типов

Ваши объекты могут участвовать в математических операциях, если у них определены  специальные методы. Например, __mul__ позволяет умножать объект на число по определенной программистом логике:

Другие специальные методы

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

Эти методы могут эмулировать поведение встроенных классов, но при этом они необязательно существуют у самих встроенных классов. Например, у объектов int при сложении не вызывается метод __add__. Таким образом, их нельзя переопределить.

Принципы ООП на Python

Рассмотрим «большую тройку» объектно-ориентированной концепции: инкапсуляцию, полиморфизм и наследование.

Инкапсуляция

Все объекты в Python инкапсулируют внутри себя данные и методы работы с ними, предоставляя публичные интерфейсы для взаимодействия.

Атрибут может быть объявлен приватным (внутренним) с помощью нижнего подчеркивания перед именем, но настоящего скрытия на самом деле не происходит – все на уровне соглашений.

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

Специальные свойства и методы класса, некоторые из которых вам уже знакомы, имеют двойные подчеркивания до и после имени.

Кроме прямого доступа к атрибутам (obj.attrName), могут быть использованы специальные методы доступа (геттеры, сеттеры и деструкторы):

Такой подход очень удобен, если получение или установка значения атрибута требует сложной логики.

Вместо того чтобы вручную создавать геттеры и сеттеры для каждого атрибута, можно перегрузить встроенные методы __getattr__, __setattr__ и __delattr__. Например, так можно перехватить обращение к свойствам и методам, которых в объекте не существует:

__getattribute__ перехватывает все обращения (в том числе и к существующим атрибутам):

Таким образом, можно организовать динамический доступ к методам и свойствам объекта, как действуют, например,  RPC-системы.

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

Язык программирования Python реализует как стандартное одиночное наследование:

так и множественное:

ООП на Python

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

Ассоциация

Кроме наследования, существует и другой способ организации межклассового взаимодействия – ассоциация (агрегация или композиция), при которой один класс является полем другого.

Пример композиции:

Пример агрегации:

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

Полиморфизм

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

Впрочем, можно получить и доступ к методам класса-предка либо по прямому обращению, либо с помощью функции super:

Одинаковый интерфейс с разной реализацией могут иметь и классы, не связанные родственными узами. В следующем примере код может одинаково удобно работать с классами English и French, так как они обладают одинаковым интерфейсом:

Это возможно благодаря утиной типизации.

Множественная диспетчеризация

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

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

Подробнее о множественной диспетчеризации читайте в этой статье из серии «Очаровательный Python».

Порядок разрешения доступа к атрибутам

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

Каким же образом интерпретатор разрешает сложные запросы к свойствам и методам? Рассмотрим последовательность поиска на примере запроса obj.field:

  1. Вызов obj.__getattribute__('field'), если он определен. При установке или удалении атрибута проверяется соответственно наличие __setattr__ или __delattr__.
  2. Поиск в obj.__dict__ (пользовательские атрибуты).
  3. Поиск в object.__class__.__slots__.
  4. Рекурсивный поиск в поле __dict__ всех родительских классов. Если класс имеет несколько предков, порядок проверки соответствует порядку их перечисления в определении.
  5. Если определен метод __getattr__, то происходит вызов obj.__getattr__('field')
  6. Выбрасывается исключение несуществующего атрибута – AttributeError.

Наконец, когда атрибут нашелся, проверяется наличие метода __get__ (при установке – __set__, при удалении – __delete__).

Все эти проверки совершаются только для пользовательских атрибутов.

Метаклассы

Метаклассы – это классы, инстансы которых тоже являются классами.

Узнайте больше о метаклассах в Python из нашего материала.

ООП на Python

Подведем краткий итог всему вышесказанному и выделим основные особенности реализации ООП на Python:

  • Классы в Python – это тоже объекты.
  • Допустимо динамическое изменение и добавление атрибутов классов.
  • Жизненным циклом объекта можно управлять.
  • Многие операторы могут быть перезагружены.
  • Многие методы встроенных объектов можно эмулировать.
  • Для скрытия внутренних данных используются синтаксические соглашения.
  • Поддерживается наследование.
  • Полиморфизм обеспечивается виртуальностью всех методов.
  • Доступно метапрограммирование.

Хотите лучше разобраться в объектно-ориентированном программировании? Смотрите понятный видеокурс по основам ООП.

Лучшие материалы и книги по Python:

Хотите получать больше интересных материалов с доставкой?

Подпишитесь на нашу рассылку:

И не беспокойтесь, мы тоже не любим спам. Отписаться можно в любое время.




Добавить комментарий