+--------------------------+
|.------------------------.|
|| kee_reel@blog:~$ cd    ||
|| си python терминал     ||
|| opengl sql             ||
||                        ||
||                обо_мне ||
|.------------------------.|
+-::--------------------::-+
.--------------------------.
 // /ooooooooooooooooooooo\\ \\ 
 // /ooooooooooooooooooooooo\\ \\ 
//------------------------------\\
\\------------------------------//

Python. Классы

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

Объектно-ориентированное программирование (ООП) – подход к написанию программ при котором:

  • Программа представляется в виде набора объектов и связей между ними
  • Все объекты являются экземплярами классов
  • Классы образуют иерархию наследования (про это в следующей статье)

Сейчас расскажу подробно что это всё значит.

Объект

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

Как это всё может относиться к программированию?

До сих пор, твои программы представляли собой набор функций, которые вызывают друг друга в определённой последовательности (или просто одна функция). Этот подход называется функциональным – он хорошо себя показывает если нужно написать программу, которая выполняет только одну задачу.

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

Вот пример простой программы “Список рецептов”, в которой можно только создавать, изменять и удалять рецепты:

Программа "Список рецептов"

Всё что я сделал – это перенёс функции программы по разным объектам.

Чтобы добавить ещё больше контекста – вот примеры задач, которые я мог бы решать в этой программе, как программист:

  • Нужно чтобы теперь файл базы данных лежал в другой папке:
    • Залезу в код объекта “База Данных”:
      • Поменяю путь до файла
  • Нужно увеличить размер кнопок в интерфейсе на 50%:
    • Залезу в код объекта “Пользовательский Интерфейс”:
      • Поменяю свойство размера кнопок
  • Нужно добавить для рецепта новое численное поле “Время приготовления”:
    • Залезу в код объекта “Пользовательский Интерфейс”:
      • Добавлю новое численное поле ввода
      • Подпишу его “Время приготовления”
      • Добавлю дополнительный параметр cooking_duration в dict, который отправляю в объект “Список Рецептов”
    • Залезу в код объекта “Список Рецептов”:
      • Добавлю логику получения значения cooking_duration из данных от объекта “Пользовательский Интерфейс”
      • Изменю запросы на добавление/изменение/удаление в объект “База Данных”, чтобы они учитывали новое значение cooking_duration

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

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

Класс

Класс – это описание объекта, а объект – это существующий экземпляр класса.

Класс Стул

То есть если мы описали какой-то класс, мы не можем его использовать по назначению – нельзя сесть на описание стула, можно сесть только на экземпляр стула.

Вот пример объявления класса, создания и использования объектов в Python:

# Создание класса
class Chair:

    def __init__(self, legs, seat, back):
        self.legs = legs
        self.seat = seat
        self.back = back

    def take_a_seat(self):
        print(f'Ты сел на стул у которого {self.legs} ножки, материал сидения - {self.seat}, а материал спинки - {self.back}')

# Создание объектов
wooden_chair = Chair(4, 'дерево', 'дерево')
plastic_chair = Chair(4, 'пластик', 'пластик')
metal_with_plastic_chair = Chair(3, 'металл', 'пластик')

# Вызов методов объектов
wooden_chair.take_a_seat()
plastic_chair.take_a_seat()
metal_with_plastic_chair.take_a_seat()
print(wooden_chair.compare_legs_count(4))
print(metal_with_plastic_chair.compare_legs_count(4))

# Обращение к полям объектов
legs = wooden_chair.legs
back = plastic_chair.back
seat = metal_with_plastic_chair.seat
print(f'Количество ножек деревянного стула: {legs}, материал спинки пластикового стула: {back}, материал сидения стула металл/пластик: {seat}')
metal_with_plastic_chair.legs = 4
print(f'Количество ножек деревянного стула: {legs}, материал спинки пластикового стула: {back}, материал сидения стула металл/пластик: {seat}')

Скопируй это код себе, и давай разберём его.

Объявление класса

class Chair:
    def __init__(self, legs, seat, back):
        self.legs = legs
        self.seat = seat
        self.back = back
    def take_a_seat(self):
        print(f'Ты сел на стул у которого {self.legs} ножки, материал сидения - {self.seat}, а материал спинки - {self.back}')

    def compare_legs_count(self, legs_to_compare):
        return self.legs == legs_to_compare:

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

class Chair:

Затем идёт определение метода __init__ – этот метод также называется конструктором объекта.

Метод – это функция, которая объявлена внутри какого-то класса.

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

В круглых скобках после названия как и в функции указываются параметры. В методах есть одна особенность – во всех методах класса первым параметром передаётся self. Именно через self мы будем обращаться к объекту и его полям.

Поле объекта – это переменная, которая хранится внутри этого объекта. У каждого объекта свой набор полей.

def __init__(self, legs, seat, back):

В методе __init__ мы создаём и заполняем поля legs, seat и back значениями, которые были переданы в параметрах конструктора.

# Поле legs -- хранит число ножек стула
self.legs = legs
# Поле seat -- хранит строку с названием материала сидения
self.seat = seat
# Поле back -- хранит строку с названием материала спинки
self.back = back

На этом логика конструктора заканчивается.

Кроме этого, мы добавляем метод take_a_seat, который “симулирует” процесс восседания на стул. В нём просто выводятся значения полей объекта.

def take_a_seat(self):
    print(f'Ты сел на стул у которого {self.legs} ножки, материал сидения - {self.seat}, а материал спинки - {self.back}')

И ещё один метод compare_legs_count, который сравнивает количество ног стула с переданным через параметры. Я его добавил, чтобы показать как выглядят методы с параметрами.

def compare_legs_count(self, legs_to_compare):
    return self.legs == legs_to_compare:

Создание объекта

Далее, мы создаём объекты – экземпляры класса Chair.

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

wooden_chair = Chair(4, 'дерево', 'дерево')
plastic_chair = Chair(4, 'пластик', 'пластик')
metal_with_plastic_chair = Chair(3, 'металл', 'пластик')

Таким образом в переменную запишется объект.

Окей, а что с ними делать?

Вызов методов класса

Помнишь мы создали методы take_a_seat и compare_legs_count? Так вот, мы можем их вызывать:

wooden_chair.take_a_seat()
# Вывод: Ты сел на стул у которого 4 ножки, материал сидения - дерево, а материал спинки - дерево
plastic_chair.take_a_seat()
# Вывод: Ты сел на стул у которого 4 ножки, материал сидения - пластик, а материал спинки - пластик
metal_with_plastic_chair.take_a_seat()
# Вывод: Ты сел на стул у которого 3 ножки, материал сидения - металл, а материал спинки - пластик
print(wooden_chair.compare_legs_count(4))
# Вывод: True
print(metal_with_plastic_chair.compare_legs_count(4))
# Вывод: False

Обрати внимание, что в зависимости от объекта, у нас получается разный вывод – мы же при создании объектов передавали разные параметры.

Мы никогда не передаём параметр self, так как Python сам его докидывает – ты же указывай всё остальное.

Обращение к полям класса

Чтобы обратиться к полю класса, надо написать “ОБЪЕКТ.ПОЛЕ”. С нимим можно работать как с обычными переменными, в них нет ничего особенного – просто они лежат в объекте.

# Можем вытаскивать значения из полей
legs = wooden_chair.legs
back = plastic_chair.back
seat = metal_with_plastic_chair.seat
print(f'Количество ножек деревянного стула: {legs}, материал спинки пластикового стула: {back}, материал сидения стула металл/пластик: {seat}')
# Можем присваивать
metal_with_plastic_chair.legs = 4
legs = wooden_chair.legs
print(f'Количество ножек деревянного стула: {legs}, материал спинки пластикового стула: {back}, материал сидения стула металл/пластик: {seat}')

Задание на закрепление

Создай класс Calculator:

  • В нём должно быть поле “acc”, в которое в конструкторе класса присваивается 0
  • В нём должны быть методы:
    • add – принимает один параметр, складывает его с “acc” и кладёт результат в “acc”
    • sub – принимает один параметр, вычитает его из “acc” и кладёт результат в “acc”
    • mul – принимает один параметр, умножает его на “acc” и кладёт результат в acc
    • div – принимает один параметр, делит “acc” на него и кладёт результат в acc
    • clr – не принимает параметров, сбрасывает значение поля acc в 0
    • res – не принимает параметров, возвращает значение поля acc

После того, как создашь класс Calculator, исполни этот код:

c = Calculator()
c.sub(10)
assert c.res() == -10
c.add(12)
assert c.res() == 2
c.add(4)
assert c.res() == 6
c.mul(2)
assert c.res() == 12
c.div(4)
assert c.res() == 3
c.clr()
assert c.res() == 0

Если посыпятся ошибки, то класс создан не по ТЗ :)

Заключение

Итого, мы изучили:

  • Объектно-ориентированное программирование
  • Объекты
  • Классы (методы, конструктор, поля)

Теперь первые два пункта в определении ООП понятны:

Объектно-ориентированное программирование (ООП) – подход к написанию программ при котором:

  • Программа представляется в виде набора объектов и связей между ними
  • Все объекты являются экземплярами классов
  • Классы образуют иерархию наследования

Если что – пиши, я помогу и постараюсь объяснить лучше.

Дальше мы залезем поглубже в принципы ООП – сложные, но полезные понятия.