Первое знакомство с классами в Python

Разработка на Python Просмотров: 503

User Rating: 0 / 5

Всем привет.

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

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

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

Кратко про объектно-ориентированное программирование

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

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

Если мы перенесём пример с фруктами на язык Python и поддержку парадигмы ООП в нём, то мы могли бы написать код таким образом, чтобы задать свой класс фрукт, затем определить в этом классе различные свойства (т.е. свойства фрукта), например, такие как: "название фрукта", "цвет", "вес", "вкус", "наличие косточки внутри" и так далее. Мы также могли бы задать некоторые действия, или методы, которые могут применяться к фруктам. Например, мы можем наделить класс фрукт такими методами: "созревать", "высыхать" и так далее.

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

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

Пример класса на Python

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

Рассмотрим на примере. Предположим, мы хотим определить класс Robot, который бы описывал некоторого робота и задавал свойства, присущие роботам. Каждому свойству зададим имя:

Теперь мы хотим определить методы, присущие роботам:

Ещё мы хотим, чтобы все наши роботы впоследствии вычисляли скорость их бега на основании их текущей скорости ходьбы. Поэтому мы введём некоторый коэффициент скорости для бега робота, и назовём его running_speed_coefficient в классе Robot. У этого коэффициента зададим тип данных float (число с плавающей точкой или, проще, десятичная дробь). Это статическая переменная класса, и данный коэффициент будет у нас общим для всех последующих экземпляров класса Robot. Когда некоторая переменная класса является общей для всех экземпляров класса, говорят о том, что это статическая переменная класса. Это означает, что как только мы изменим её значение, это значение будет изменено на уровне класса, а не его экземпляров. При этом каждый уже созданный экземпляр класса Robot будет использовать новое, изменённое значение.

Вот как наш класс робота может выглядеть на языке Python:

Info icon by Icons8НА ЗАМЕТКУ
Обратите внимание, что класс и его методы снабжены документацией, т.е. комментариями, описывающими назначение нашего класса и каждого его метода. Это удобно, чтобы впоследствии вспомнить самим, за что отвечает каждый метод класса, а также дать возможность другим разработчикам, которые будут пользоваться классом, понять, как он устроен.

class Robot:
    """
    [RU] Класс, описывающий робота, у которого есть свойства: название робота (модель), количество рук,
    количество ног, высота робота (в метрах), вес робота (в килограммах), скорость ходьбы робота.
    [EN] A class that describes a robot that has specified properties: robot name (model), number of arms,
    number of legs, height of the robot (in meters), weight of the robot (in kilograms), walking speed of the robot.
    """
    __doc__ = "Robot - класс для создания экземпляров различных роботов"
    running_speed_coefficient: float = 1.5

    def __init__(self, name: str, arms: int, legs: int, height: int, weight: int, walking_speed: int):
        self.name: str = name
        self.arms: int = arms
        self.legs: int = legs
        self.height: int = height
        self.weight: int = weight
        self.walking_speed: int = walking_speed
        self._is_running: bool = False

    def _check_if_zero_speed(self, error_message_if_no_speed):
        """
        [RU] Закрытый метод класса Robot. Проверяет текущую скорость ходьбы робота и возвращает True, если она равна 0. В противном случае возвращает False;
        [EN] Private method of the Robot class. Checks the robot's current walking speed and returns True if it is 0. Otherwise returns False.

        :param error_message_if_no_speed: [RU] сообщение об ошибке, если скорость робота равна 0; [EN] error message if robot speed is 0
        :return: [RU] True, если скорость ходьбы робота равна 0, иначе False;
                 [EN] True if the robot's walking speed is 0, otherwise False.
        """
        if self.walking_speed == 0:
            print(error_message_if_no_speed)
            return True
        return False

    def walk(self):
        """
        [RU] Метод предписывает роботу начать движение (ходьбу) с текущей скоростью.
        Если текущая скорость робота равна 0, то будет показана ошибка;
        [EN] The method instructs the robot to start moving (walking) at the current speed.
        If the robot's current speed is 0, an error will be shown.
        """
        if not self._check_if_zero_speed(f'robot.walk(): Робот не может начать ходьбу, т.к. его скорость равна 0 км/ч'):
            print(f'robot.walk(): Робот пошёл со скоростью {self.walking_speed} км/ч')
            self._is_running = False

    def run(self):
        """
        [RU] Метод предписывает роботу начать бег с текущей скоростью ходьбы, умноженной на коэффициент
        running_speed_coefficient. Если текущая скорость робота равна 0, то будет показана ошибка;
        [EN] The method instructs the robot to start running at the current walking speed multiplied by the coefficient
        running_speed_coefficient. If the robot's current speed is 0, an error will be shown.
        """
        if not self._check_if_zero_speed(f'robot.run(): Робот не может начать бег, т.к. его скорость равна 0 км/ч'):
            print(f'robot.run(): Робот побежал со скоростью {self._get_run_speed()} км/ч')
            self._is_running = True

    def set_walking_speed(self, new_walking_speed: int):
        """
        [RU] Метод устанавливает скорость ходьбы для робота;
        [EN] The method sets the walking speed for the robot.

        :param new_walking_speed: [RU] новая скорость ходьбы для робота; [EN] new walking speed for the robot
        """
        self.walking_speed = new_walking_speed
        print(f'robot.set_speed({new_walking_speed}): Установлена новая скорость для робота: {self.walking_speed} км/ч')

    def _get_run_speed(self):
        """
        [RU] Закрытый метод. Возвращает текущую скорость бега робота;
        [EN] Private method. Returns the robot's current running speed.
        :return: [RU] значение текущей скорости бега робота; [EN] value of the robot's current running speed.
        """
        return self.walking_speed * self.running_speed_coefficient

    def jump(self):
        """
        [RU] Метод предписывает роботу прыгнуть, независимо от того, находится ли робот в движении или стоит;
        [EN] The method instructs the robot to jump, regardless of whether the robot is moving or standing.
        """
        if self.walking_speed == 0:
            print('robot.jump(): Робот подпрыгнул на месте.')
        elif self._is_running:
            print(f'robot.jump(): Робот подпрыгнул на бегу, двигаясь со скоростью {self._get_run_speed()} км/ч')
        else:
            print(f'robot.jump(): Робот подпрыгнул при ходьбе, двигаясь со скоростью {self.walking_speed} км/ч')

    def show_info(self):
        """
        [RU] Отобразить информацию о текущем экземпляре робота;
        [EN] Display information about the current robot instance
        """
        print('---')
        print('robot.show_info(): Вывод информации о текущем экземпляре класса Robot:')
        print(f'\tНазвание робота (модель): {self.name}')
        print(f'\tКоличество рук у робота: {self.arms}')
        print(f'\tКоличество ног у робота: {self.legs}')
        print(f'\tВысота робота (метров): {self.height}')
        print(f'\tВес робота (кг): {self.weight}')
        print(f'\tТекущая скорость ходьбы робота (км/ч): {self.walking_speed}')
        print(f'\tТекущая скорость бега робота (км/ч): {self._get_run_speed()}')
        print('---')

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

Если метод принимает только self на вход, как, например, методы класса walk(self)run(self)jump(self), то при вызове этих методов для самого объекта класса не потребуется указывать каких-то параметров (мы увидим это чуть далее по тексту примера).

Также обратите внимание на два метода класса: _get_run_speed(self) и _check_if_zero_speed(self, error_message_if_no_speed). Они начинаются с символа нижнего подчёркивания , и это ещё одна конвенция в Python: таким образом методы класса помечаются как закрытые (приватные), т.е. эти методы можно вызвать только из самих методов класса, но не на объекте класса.

Теперь, когда наш класс Robot готов, давайте создадим объекты этого класса, т.е. сами объекты роботов. Начнём мы с создания экземпляра робота, который назовём "Робот прямоходящий", а переменную для него назовём robot1:

    # создать экземпляр робота со следующими параметрами:
    # количество рук: 2, количество ног: 2, высота (метров): 2.5, вес (кг): 1500, текущая скорость (км/ч): 0
    robot1 = Robot("Робот прямоходящий", 2, 2, 2.5, 1500, 0)

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

Далее мы можем вызывать методы у объекта робота с именем robot1. Например, так:

    # показать информацию об экземпляре созданного робота
    robot1.show_info()

Вызов метода show_info() на объекте робота robot1 приведёт при запуске скрипта Python к отображению информации об экземпляре робота, и распечатает основные значения для свойств этого робота.

Давайте вызовем другие методы класса Robot, которые мы определили ранее. Заставим объект робота robot1 прыгнуть, затем пойти:

    # вызвать метод прыжка у робота
    robot1.jump()

    # вызвать метод ходьбы для робота. Сразу пойти роботу не удастся, поскольку его текущая скорость равна 0
    robot1.walk()

Пойти роботу не удастся, поскольку его начальная скорость равна 0. Исправим это, установив роботу скорость 5 км/ч:

    # установить скорость для робота 5 км/ч
    robot1.set_walking_speed(5)

Теперь вызовем снова метод jump() для объекта робота. Поскольку метод jump() внутри его реализации в классе Robot проверяет - в каком состоянии сейчас находится робот (стоит/идёт/бежит), то при вызове jump() с ненулевой скоростью робота, мы увидим соответствующее сообщение при запуске скрипта. После этого вызовем повторно методы show_info()run() и jump() для объекта робота:

    # вызвать метод прыжка, когда робот идёт
    robot1.jump()

    # повторно показать информацию об экземпляре робота
    robot1.show_info()

    # вызвать метод бега для робота.
    robot1.run()

    # вызвать метод прыжка, когда робот бежит
    robot1.jump()

Теперь создадим другой объект второго робота с именем "Робот-паук" (у него будет 6 ног и 4 руки), а в качестве имени переменной для этого экземпляра робота выберем robot2. Обратите внимание, что в этот раз мы напрямую указываем имена параметров, задавая им значения:

    # создаём второго робота
    robot2 = Robot(name="Робот-паук", arms=4, legs=6, height=1.5, weight=2500, walk_speed=30)

    # выводим информацию о втором роботе
    robot2.show_info()

    # изменили значение статической переменной running_speed_coefficient,
    # которая изменит скорость бега для всех роботов
    Robot.running_speed_coefficient = 2.5

    # повторно вывести информацию об обоих роботах, чтобы убедиться, что их
    # скорость бега теперь увеличена
    robot1.show_info()
    robot2.show_info()

После создания экземпляра робота мы вызываем для него метод show_info(), чтобы вывести на консоль основные свойства этого робота.

Дальше показан пример того, как мы меняем общее для всех роботов свойство класса Robot - коэффициент скорости для бега, представленное статической переменной running_speed_coefficient, устанавливая ей значение 2.5. После этого мы для каждого робота вызываем show_info(), чтобы увидеть эффект от изменения коэффициента.

Если теперь запустить скрипт (предполагается, что вы используете наш репозиторий примеров на Python, ссылка на который указана внизу статьи), то на экране можно будет увидеть следующее:

======================================================================================================
>>> [RU] Запуск примера из статьи: https://allineed.ru/development/python-development/94-python-classes-basics
>>> [EN] Running the sample from the article: https://allineed.ru/development/python-development/94-python-classes-basics
======================================================================================================
Robot - класс для создания экземпляров различных роботов
---
robot.show_info(): Вывод информации о текущем экземпляре класса Robot:
Название робота (модель): Робот прямоходящий
Количество рук у робота: 2
Количество ног у робота: 2
Высота робота (метров): 2.5
Вес робота (кг): 1500
Текущая скорость ходьбы робота (км/ч): 0
Текущая скорость бега робота (км/ч): 0.0
---
robot.jump(): Робот подпрыгнул на месте.
robot.walk(): Робот не может начать ходьбу, т.к. его скорость равна 0 км/ч
robot.set_speed(5): Установлена новая скорость для робота: 5 км/ч
robot.jump(): Робот подпрыгнул при ходьбе, двигаясь со скоростью 5 км/ч
---
robot.show_info(): Вывод информации о текущем экземпляре класса Robot:
Название робота (модель): Робот прямоходящий
Количество рук у робота: 2
Количество ног у робота: 2
Высота робота (метров): 2.5
Вес робота (кг): 1500
Текущая скорость ходьбы робота (км/ч): 5
Текущая скорость бега робота (км/ч): 7.5
---
robot.run(): Робот побежал со скоростью 7.5 км/ч
robot.jump(): Робот подпрыгнул на бегу, двигаясь со скоростью 7.5 км/ч
---
robot.show_info(): Вывод информации о текущем экземпляре класса Robot:
Название робота (модель): Робот-паук
Количество рук у робота: 4
Количество ног у робота: 6
Высота робота (метров): 1.5
Вес робота (кг): 2500
Текущая скорость ходьбы робота (км/ч): 30
Текущая скорость бега робота (км/ч): 45.0
---
---
robot.show_info(): Вывод информации о текущем экземпляре класса Robot:
Название робота (модель): Робот прямоходящий
Количество рук у робота: 2
Количество ног у робота: 2
Высота робота (метров): 2.5
Вес робота (кг): 1500
Текущая скорость ходьбы робота (км/ч): 5
Текущая скорость бега робота (км/ч): 12.5
---
---
robot.show_info(): Вывод информации о текущем экземпляре класса Robot:
Название робота (модель): Робот-паук
Количество рук у робота: 4
Количество ног у робота: 6
Высота робота (метров): 1.5
Вес робота (кг): 2500
Текущая скорость ходьбы робота (км/ч): 30
Текущая скорость бега робота (км/ч): 75.0
---

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

В пакете ain_class_example нашего репозитория с примерами на Python вы найдете 3 файла:

Заключение. Упражнения для закрепления материала.

В качестве упражнения и закрепления навыков работы с классами в Python я рекомендую читателям попробовать создать самостоятельно два новых класса Dog (собака) и Cat (кошка).

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

В классе Cat определить конструктор, который сможет создавать экземпляр кошки по всем свойствам класса, а также следующие свойства и методы:

Попробуйте также кратко задокументировать работу методов в данных классах, чтобы описать назначение методов в классах Dog и Cat. Для каждого метода напишите краткий комментарий о том, что он делает. Пример документирования методов класса вы можете найти в классе Robot, который мы рассмотрели с вами в статье.

Также создайте отдельный модуль на Python, куда импортируйте модуль (или модули, если вы решите создать классы в разных модулях), где описаны ваши классы Cat и Dog. Создайте несколько экземпляров собак и кошек, придумав им имена и значения для свойств. Вызовите методы и посмотрите на результат их работы на экране консоли.

А на сегодня всё, спасибо за внимание и удачи в создании классов на Python.