71. Основы объектно-ориентированного программирования (ООП) в Python
Оглавление урока
Введение
В предыдущем уроке мы закончили разбираться с модулями и пакетами в Python. Начиная с этого урока, мы погрузимся в объектно-ориентированное программирование (ООП) в Python.
Ранее мы все называли «объектами» в общем смысле. В этом разделе начнем изучать классы Python – механизм для создания новых объектов, поддерживающих наследование. Класс - столп объектно-ориентированного программирования, помогающий разбить большую программу на смысловые независимые части для уменьшения избыточности кода и возможности повторного использования путем настройки кода, а не переписывания его. На первый взгляд, это может напоминать функции, но на самом деле класс более гибкий инструмент.
Python является таким языком, в котором применение ООП не является обязательным, особенно в начале изучения (а если вы на этом уроке, то уже давно не в начале изучения языка Python), так как чаще всего достаточно применение простых конструкций, например, функций. Если программа начинается разрастаться, то уже стоит задуматься о применении ООП, при надлежащем применении которого можно значительно уменьшить время разработки и упростить дальнейшую поддержку и модернизацию существующего кода.
Если рассматривать программу как автомобиль, то отдельные ее составные части могут быть классами, выражаясь в терминах ООП – это композиция. Четыре колеса можно описать одним классом, в котором есть два метода класса (пока определим это понятие как функция, находящаяся внутри класса): вращение колеса вперед и назад и один атрибут класса (пока определим, как переменные внутри класса): давление в шинах.
Все четыре колеса могут вращаться как вперед, так и назад. Два из них могут поворачиваться влево/вправо. Отвлечемся и посмотрим на пример класса Wheel
. Кстати, пользовательские классы принято называть с заглавной буквы.
class Wheel:
pressure = 2.0
def forward_rotation(self):
pass
def backwards_rotation(self):
pass
Чтобы решить проблему с поворотом колес влево/вправо, просто дописать два метода в этот класс недопустимо, так как таким образом получается, что и задние колеса могут поворачиваться. Можно создать второй класс, который будет дублировать два метода и атрибут и к тому же иметь два дополнительных метода, связанных с поворотом колес. Предположим, мы сделаем так и в какой-то момент нам станет нужно добавить новый атрибут, обозначающий диаметр дисков. Нам придется добавить этот атрибут в оба класса. Так можно и запутаться или забыть что-то куда-то дописать. Отметаем этот вариант. Остается наследование. Мы можем создать класс с общими свойствами, как класс Wheel
и наследовать от него другие классы FrontWheel
и BackWheel
, которые будут обладать теми же методами/аргументами класса Wheel
и иметь свои специализированные. Такой способ сведет к минимуму дублирование кода и упростит внесение общих изменений. Подробнее про наследование будем говорить позднее.
Классы напоминают модули и функции, они точно так же являются средством для упаковки логики и данных и определяют новое пространство имен. Но есть отличия. Во-первых, у одного класса может быть несколько экземпляров (объект, обладающий свойствами класса). Для примера давайте создадим четыре колеса (экземпляра) от класса Wheel
:
fl = Wheel()
fr = Wheel()
bl = Wheel()
br = Wheel()
Далее можем вращать левое переднее колесо вперед и узнать его давление:
fl.forward_rotation()
print(fl.pressure) # => 2.0
Во-вторых, классы в отличие от функций и модулей поддерживают наследование. В-третьих, классы поддерживают перегрузку операторов, позволяющую задать свою реализацию для какого-либо метода (подробнее об этом будет говорить позднее).
Если вы внимательно рассмотрели пример класса Wheel
, то заметили: метод не совсем обычная функция. Метод класса должен иметь первый аргумент self
(для получения объекта, на котором был произведен вызов). Рассмотрим пример:
class Hello:
def print_name(self, name):
print(name)
user = Hello()
user.print_name('Иван')
В первый аргумент self
не нужно ничего передавать. Имя «Иван
» было передано первому аргументу, сразу после self
.
Конструктор класса и перегрузка операций
Прежде чем перейти к конструктору класса, разберемся с тем, как обращаться к атрибутам класса внутри его самого. Создадим атрибут name
, в котором будем хранить имя и метод для установления этого имени.
class Hello:
name = ''
def setname(self, n):
name = n
user_1 = Hello()
user_2 = Hello()
user_1.setname('Иван')
user_2.setname('Петр')
print(user_1.name)
Почему ничего не вывелось? Чтобы обратиться к атрибуту класса, необходимо использовать ключевое слово self
. Исправим наш код:
class Hello:
name = ''
def setname(self, n):
self.name = n
Теперь плавно переходим к конструктору класса. Каждый раз, когда экземпляр (в примере выше user_1
) генерируется из класса, Python вызывает метод __init__
- конструктор класса. Конструктор класса __init__
может не указываться явно, как это было в примерах выше. Рассмотрим пример с явным указанием конструктора класса, который принимает один параметр и устанавливает его в атрибут name.
class Hello:
name = ''
def __init__(self, n):
self.name = n
def print_name(self):
print(self.name)
user_1 = Hello('Иван')
user_1.print_name()
Обратите внимание на строку создания экземпляра класса, а именно на то, как и куда передаем параметр «Иван
». Этот пример показывает не только работу конструктора класса, но и перегрузку операторов, о которой мы будем боле подробно говорить в дальнейшем.
В этом уроке мы очень кратко познакомились с классами и ООП, чтобы только очертить наше дальнейшее изучение. Как вы наверняка поняли, ООП – это способ упростить построение и поддержку больших программ, тем самым сэкономив время.