Объектно-ориентированное Программирование в Python

Объектно-ориентированное программирование (ООП) — это парадигма программирования, где различные компоненты компьютерной программы моделируются на основе реальных объектов. Объект — это что-либо, у чего есть какие-либо характеристики и то, что может выполнить какую-либо функцию.

Содержание

Представьте сценарий, где вам нужно разработать болид Формулы-1 используя подход объектно-ориентированного программирования. Первое, что вам нужно сделать — это определить реальные объекты в настоящей гонке Формула-1. Какие аспекты в Формуле-1 обладают определенными характеристиками и могут выполнять ту или иную функцию?

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!

Один из очевидных ответов на этот вопрос — гоночный болид. Условный болид может обладать такими характеристиками как:

  • мощность двигателя;
  • марка;
  • модель;
  • производитель, и т. д.

Соответственно, болид можно запустить, остановить, ускорить, и так далее. Гонщик может быть еще одним объектом в Формуле-1. Гонщик имеет национальность, возраст, пол, и так далее, кроме этого, он обладает таким функционалом, как управление болидом, рулевое управление, переключение передач.

Как и в этом примере, в объектно-ориентированном программировании мы создадим объекты, которые будут соответствовать реальным аспектам.

Стоит обратить внимание на то, что объектно-ориентированное программирование — не зависящая от языка программирования концепция. Это общая концепция программирования и большинство современных языков, такие как Java, C#, C++ и Python поддерживают объектно-ориентированное программирование.

В этой статье мы разберем подробную инструкцию объектно-ориентированного программирования в Python, но перед этим, рассмотрим некоторые преимущества и недостатки объектно-ориентированного программирования.

Преимущества и недостатки ООП Python

Рассмотрим несколько основных преимуществ объектно-ориентированного программирования:

  1. Объектно-ориентированное программирование подразумевает повторное использование. Компьютерная программа написанная в форме объектов и классов может быть использована снова в других проектах без повторения кода;
  2. Использование модулярного подхода в объектно-ориентированном программировании позволяет получить читаемый и гибкий код;
  3. В объектно-ориентированном программировании каждый класс имеет определенную задачу. Если ошибка возникнет в одной части кода, вы можете исправить ее локально, без необходимости вмешиваться в другие части кода;
  4. Инкапсуляция данных (которую мы рассмотрим дальше в статье) вносит дополнительный уровень безопасности в разрабатываемую программу с использованием объектно-ориентированного подхода;

Хотя объектно-ориентированное программирование обладает рядом преимуществ, оно также содержит определенные недостатки, некоторые из них находятся в списке ниже:

  1. Для создания объектов необходимо иметь подробное представление о разрабатываемом программном обеспечении;
  2. Не каждый аспект программного обеспечения является лучшим решением для реализации в качестве объекта. Для новичков может быть тяжело прочертить линию в золотой середине;
  3. С тем, как вы вносите все новые и новые классы в код, размер и сложность программы растет в геометрической прогрессии;

В следующем разделе мы рассмотрим ряд самых важных концепций объектно-ориентированного программирования.

Как и следует из названия, объектно-ориентированное программирование — это речь об объектах. Однако, перед тем как создать объект, нам нужно определить его класс.

Класс

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

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

Отношение между классом и объектом можно представить более наглядно, взглянув на отношение между машиной и Audi. Да, Audi – это машина. Однако, нет такой вещи, как просто машина. Машина — это абстрактная концепция, которую также реализуют в Toyota, Honda, Ferrari, и других компаниях.

Ключевое слово class используется для создания класса в Python. Название класса следует за ключом class, за которым следует двоеточие. Тело класса начинается с новой строки, с отступом на одну вкладку влево.

Давайте рассмотрим, как мы можем создать самый простой класс в Python. Взглянем на следующий код:

В примере выше мы создали класс под названием Car с тремя атрибутами: имя name, марка make и модель model. Наш класс также содержит два метода: start() и stop().

Объекты

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

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

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

Давайте создадим объект класса Car, который мы создали в предыдущем разделе.

В этом скрипте мы создали два объекта класса Car: car_a и car_b. Чтобы узнать тип созданных нами объектов, мы можем использовать метод type и передать ему названия наших объектов. Выполните следующий код:

В выдаче вы увидите:

Это говорит нам о том, что тип объекта car_b – класс Car.

На данный момент мы создали наш класс и соответствующие ему объекты. Теперь настало время получить доступ к атрибутам класса и вызвать метод класса при помощи объекта класса. Чтобы сделать это, вам нужно только вписать имя объекта, за которым следует оператор точка . и название атрибута или метода, к которому вы хотите получить доступ или вызов, соответственно. Давайте рассмотрим следующий пример:

В этом скрипте мы вызываем метод start() через объект car_b. Выдача будет выглядеть следующим образом:

Аналогично, вы можете получить доступ к атрибуту, пользуясь следующим синтаксисом:

В выдаче вы увидите значение атрибута модели, как показано ниже:

Атрибуты класса

В предыдущей секции мы разобрались, как создавать объекты класса и как мы можем использовать эти объекты для получения доступа к атрибутам класса

В Python, каждый объект содержит определенные атрибуты по умолчанию и методы в дополнение к определенным пользователем атрибутами. Чтобы посмотреть на все атрибуты и методы объекта, используйте встроенную функцию под названием dir(). Попробуем взглянуть на все атрибуты объекта car_b, который мы создали в предыдущем разделе. Выполните следующий скрипт:

В выдаче вы увидите следующие атрибуты:

Эта встроенная функция очень полезна при изучении атрибутов и функций объекта, особенно при использовании через REPL.

Атрибуты класса против атрибутов экземпляров

Атрибуты могут быть наглядно отнесены к двум типам:

  • атрибуты класса
  • атрибуты экземпляров

Атрибуты класса делятся среди всех объектов класса, в то время как атрибуты экземпляров являются собственностью экземпляра.
Помните, что экземпляр — это просто альтернативное название объекта.

Атрибуты экземпляра объявляются внутри любого метода, в то время как атрибуты класса объявляются вне любого метода.

Следующий пример прояснит эту разницу:

В указанном выше скрипте мы создаем класс Car с одним атрибутом класса под названием car_count и три атрибута экземпляра под названием name, make и model. Класс содержит один метод start(), который содержит наши три атрибута экземпляров. Значения атрибутов экземпляров переданы в качестве аргументов методу start(). Внутри метода start, атрибут car_count увеличен на один.

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

Давайте создадим объект класса Car и вызовем метод start().

В скрипте выше мы вывели название атрибута экземпляра и атрибута класса car_count. В выдаче вы увидите, что атрибут car_count будет иметь значение 1, как показано ниже:

Теперь создадим еще один объект класса Car и вызываем метод start().

Сейчас если вы выведите значение атрибута car_count, вы увидите 2 в выдаче. Это связано с тем, что атрибут car_count является атрибутом класса и таким образом он разделяется между экземплярами. Объект car_a увеличил свое значение до 1, в то время как car_b увеличил свое значение еще раз, так что итоговое значение равняется 2. Выдача выглядит следующим образом:

Методы

Как мы выяснили ранее, в объектно-ориентированном программировании, методы используются для реализации функционалов объекта. В предыдущем разделе мы создали методы start() и stop() для класса Car. До этих пор, мы использовали объекты класса для вызова методов. Однако, есть тип методов, который может быть вызван напрямую при помощи имени класса. Такой метод называется статичным методом.

Статичные методы

Для объявления статического метода, вам нужно указать дескриптор @staticmethod перед названием метода, как показано ниже:

В коде выше мы создали класс Car с одним статичным методом get_class_details(). Давайте вызовем этот метод, используя название класса.

Вы можете видеть что нам не нужно создавать экземпляр класса Car для вызова метода get_class_details(), вместо этого мы просто использовали название класса. Стоит упомянуть, что статические методы могут иметь доступ только к атрибутам класса в Python, вы не сможете обратиться к методам через self.

Возврат множественных значений из метода

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

В скрипте выше мы создали класс под названием Square со статичным методом get_squares(). Метод принимает два параметра. Он умножает каждый параметр на себя и возвращает оба результата при помощи оператора return. В выдаче указанного выше скрипта вы увидите квадраты 3 и 5.

Метод str

До этого момента мы выводили атрибуты при помощи метода print(). Посмотрим, что случится, если мы выведем объект класса.

Для этого нам нужно создать простой класс Car с одним методом и попытаться вывести объект класса в консоль. Выполним следующий скрипт:

В скрипте выше мы создали объект car_a класса Car и вывели его значение на экран. По сути мы относимся к объекту car_a как к строке. Выдача выглядит следующим образом:

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

В скрипте выше, мы переопределили метод __str__ , предоставив наше собственное определение метода. Теперь, если вы выведите объект car_a, вы увидите сообщение «Car class Object» в консоли. Это сообщение, которое мы внесли в наш пользовательский метод __str__ .

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

Конструкторы

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

Для создания конструктора вам нужно создать метод с ключевым словом __init__. Взгляните на следующий пример:

В скрипте выше мы создали класс Car с одним атрибутом класса car_count. Класс содержит конструктор, который увеличивает значение car_count и выводит итоговое значение на экран.

Теперь, когда объект класса Car будет создан, конструктор также будет вызван, значение car_count увеличится и отобразится на экране. Создадим простой объект и посмотрим, что выйдет:

В выдаче вы увидите выведенное значение 1, 2 и 3, поскольку для каждого объекта значение переменной car_count увеличивается и отображается на экране.

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

Локальные переменные против глобальных

Мы знаем, что есть два типа атрибутов Python: атрибуты экземпляра и атрибуты класса.

Атрибуты класса также называются переменными. В зависимости от области видимости, переменные также могут относиться к двум типам: локальные переменные и глобальные переменные.

Локальные переменные

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

В скрипте выше мы создали локальную переменную message внутри метода start() класса Car. Теперь создадим объект класса Car и попытаемся получить доступ к локальной переменной message, как показано ниже:

Скрипт выше приводит к следующей ошибке AttributeError:

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

Глобальная переменная

Глобальная переменная определяется вне любого блока, то есть метода, операторов-if, и тому подобное. Доступ к глобальной переменной может быть получен где угодно в классе. Рассмотрим следующий пример.

В этом скрипте мы создали глобальную переменную message1 и вывели ее значение на экран. В выдаче вы увидите значение переменной message1, выведенной без ошибки.

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

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

Доступ к локальной переменной может быть получен только внутри метода. Хотя в этой статье локальные переменные и атрибуты экземпляров определяются внутри метода, локальные переменные определяются собственным ключевым словом.

Модификаторы доступа

Модификаторы доступа в Python используются для модификации области видимости переменных по умолчанию. Есть три типа модификаторов доступов в Python ООП:

  1. публичный — public;
  2. приватный — private;
  3. защищенный — protected.

Доступ к переменным с модификаторами публичного доступа открыт из любой точки вне класса, доступ к приватным переменным открыт только внутри класса, и в случае с защищенными переменными, доступ открыт только внутри того же пакета.

Для создания приватной переменной, вам нужно проставить префикс двойного подчеркивание __ с названием переменной.

Для создания защищенной переменной, вам нужно проставить префикс из одного нижнего подчеркивания _ с названием переменной. Для публичных переменных, вам не нужно проставлять префиксы вообще.

Давайте взглянем на публичные, приватные и защищенные переменные в действии. Выполните следующий скрипт:

Здесь мы создали простой класс Car с конструктором и тремя переменными: name, make, и model (название, марка и модель). Переменная name является публичной, в то время как переменные make и model являются приватными и защищенными, соответственно.

Давайте создадим объект класса Car и попытаемся получить доступ к переменной name. Выполним следующий скрипт:

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

Теперь попробуем вывести значение переменной make. Выполняем следующий скрипт:

В выдаче мы получим следующее уведомление об ошибке:

Мы рассмотрели большую часть основных концепций объектно-ориентированного программирования в предыдущих двух секциях. Теперь, поговорим о столбах объектно-ориентированного программирования:

  • Полиморфизм;
  • Наследование;
  • Инкапсуляция.

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

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

В объектно-ориентированном программировании, наследование означает отношение IS-A. Например, болид — это транспорт. Наследование это одна из самых удивительных концепций объектно-ориентированного программирования, так как оно подразумевает повторное использование.

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

Рассмотрим на очень простой пример наследования. Выполним следующий скрипт:

В скрипте выше мы создаем два класса: Vehicle и Car, который наследует класс Vehicle. Чтобы наследовать класс, вам нужно только вписать название родительского класса внутри скобок, которая следует за названием дочернего класса. Класс Vehicle содержит метод vehicle_method(), а дочерний класс содержит метод car_method(). Однако, так как класс Car наследует класс Vehicle, он также наследует и метод vehicle_method().

Рассмотрим это на практике и выполним следующий скрипт:

В этом скрипте мы создали объект класса Car вызывали метод vehicle_method() при помощи объекта класса Car. Вы можете обратить внимание на то, что класс Car не содержит ни одного метода vehicle_method(), но так как он унаследовал класс Vehicle, который содержит vehicle_method(), класс Car также будет использовать его. Выдача выглядит следующим образом:

Множественное наследование Python

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

В этом скрипте, родительский класс Vehicle наследуется двумя дочерними классами — Car и Cycle. Оба дочерних класса будут иметь доступ к vehicle_method() родительского класса. Запустите следующий скрипт, чтобы увидеть это лично:

В выдаче вы увидите выдачу метода vehicle_method() дважды, как показано ниже:

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

В скрипте выше мы создали три класса: Camera, Radio, и CellPhone. Классы Camera и Radio наследуются классом CellPhone. Это значит, что класс CellPhone будет иметь доступ к методам классов Camera и Radio. Следующий скрипт подтверждает это:

Выдача будет выглядеть следующим образом:

Полиморфизм

Термин полиморфизм буквально означает наличие нескольких форм. В контексте объектно-ориентированного программирования, полиморфизм означает способность объекта вести себя по-разному.

Полиморфизм в программировании реализуется через перегрузку метода, либо через его переопределение.

Перегрузка метода

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

В скрипте выше, если метод start() вызывается передачей одного аргумента, параметр будет выведен на экран. Однако, если мы передадим 2 аргумента методу start(), он внесет оба аргумента и выведет результат суммы.

Попробуем с одним аргументом для начала:

В выдаче мы можем видеть 10. Теперь попробуем передать два аргумента:

В выдаче вы увидите 30.

Переопределение метода

Переопределение метода относится к наличию метода с одинаковым названием в дочернем и родительском классах. Определение метода отличается в родительском и дочернем классах, но название остается тем же. Давайте посмотрим на простой пример переопределения метода в Python.

В скрипте выше, классы Cycle и Car наследуют класс Vehicle. Класс Vehicle содержит метод print_details(), который переопределен дочерним классом. Теперь, если вы вызовите метод print_details(), выдача будет зависеть от объекта, через который вызывается метод. Выполните следующий скрипт, чтобы понять суть на деле:

Выдача будет выглядеть вот так:

Как вы видите, выдача отличается, к тому же метод print_details() вызывается через производные классы одного и того же базового класса. Однако, так как дочерние классы переопределены методом родительского класса, методы ведут себя по-разному.

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

Инкапсуляция — это третий столп объектно-ориентированного программирования. Инкапсуляция просто означает скрытие данных. Как правило, в объектно-ориентированном программировании один класс не должен иметь прямого доступа к данным другого класса. Вместо этого, доступ должен контролироваться через методы класса.

Чтобы предоставить контролируемый доступ к данным класса в Python, используются модификаторы доступа и свойства. Мы уже ознакомились с тем, как действуют модификаторы доступа. В этом разделе мы посмотрим, как действуют свойства.

Предположим, что нам нужно убедиться в том, что модель автомобиля должна датироваться между 2000 и 2018 годом. Если пользователь пытается ввести значение меньше 2000 для модели автомобиля, значение автоматически установится как 2000, и если было введено значение выше 2018, оно должно установиться на 2018. Если значение находится между 2000 и 2018 — оно остается неизменным. Мы можем создать свойство атрибута модели, которое реализует эту логику. Взглянем на пример:

Свойство имеет три части. Вам нужно определить атрибут, который является моделью в скрипте выше. Затем, вам нужно определить свойство атрибута, используя декоратор @property. Наконец, вам нужно создать установщик свойства, который является дескриптором @model.setter в примере выше.

Теперь, если вы попробуете ввести значение выше 2018 в атрибуте модели, вы увидите, что значение установлено на 2018. Давайте проверим это. Выполним следующий скрипт:

Здесь мы передаем 2088 как значение для модели, однако, если вы введете значение для атрибута модели через функцию get_car_model(), вы увидите 2018 в выдаче.

Подведем итоги

В этой статье мы освоили часть важнейших основ объектно-ориентированного программирования. Этот тип программирования — один из самых популярных и используемых парадигм.

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