Методы классов

Содержание

  1. Что такое декоратор
  2. Методы класса @classmethod
  3. Создание объектов через cls
  4. Статические методы @staticmethod
  5. Защищенные и приватные атрибуты
  6. @property, getter и setter
  7. Типичные ошибки
  8. Практика

Что такое декоратор

Декоратор в Python - это способ изменить поведение функции или метода, не переписывая его тело напрямую. Декоратор пишется над функцией через @. Если функции подзабылись — вернись к 03.01 - Функции.

В этой теме нам важны встроенные декораторы:

  • @classmethod - делает метод методом класса;
  • @staticmethod - делает метод статическим;
  • @property - позволяет обращаться к методу как к атрибуту.

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


Методы класса @classmethod

Метод класса связан не с конкретным объектом, а с самим классом. Первый параметр такого метода - cls. Он указывает на класс, через который метод вызвали.

class User:
    role = "student"
 
    @classmethod
    def get_role(cls):
        return cls.role
 
print(User.get_role())

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

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

user = User()
print(user.get_role())  # работает, но лучше User.get_role()

Когда использовать методы класса

Методы класса удобны, когда нужно:

  • работать с атрибутами класса;
  • вести общий счетчик объектов;
  • создать альтернативный конструктор;
  • вернуть объект класса через cls(...);
  • написать логику, которая относится ко всему классу, а не к одному экземпляру.

Пример со счетчиком:

class Course:
    created_count = 0
 
    def __init__(self, title):
        self.title = title
        Course.created_count += 1
 
    @classmethod
    def get_created_count(cls):
        return cls.created_count
 
course1 = Course("Python")
course2 = Course("AQA")
 
print(Course.get_created_count())

Создание объектов через cls

Метод класса может создавать объект. Это часто называют альтернативным конструктором.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    @classmethod
    def from_birth_year(cls, name, birth_year):
        current_year = 2026
        age = current_year - birth_year
        return cls(name, age)
 
person = Person.from_birth_year("Ирина", 1998)
print(person.name, person.age)

В строке return cls(name, age) вызывается конструктор класса. Если этот метод унаследует дочерний класс, cls позволит создать объект именно дочернего класса.


Статические методы @staticmethod

Статический метод находится внутри класса, но не получает ни self, ни cls. По сути, это обычная функция, которую положили внутрь класса для логической группировки.

class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b
 
    @staticmethod
    def is_positive(value):
        return value > 0
 
print(MathUtils.add(6, 9))
print(MathUtils.is_positive(-3))

Такой метод не использует данные объекта и не обращается к атрибутам класса. Он просто логически относится к классу.

Использовать staticmethod удобно для утилитных действий: расчеты, форматирование, проверки, конвертация значений.


Защищенные и приватные атрибуты

В Python нет жестких модификаторов доступа как в некоторых других языках. Вместо этого используются соглашения по именам.

Одно подчеркивание _value

Одно подчеркивание означает: атрибут предназначен для внутреннего использования.

class Account:
    def __init__(self, balance):
        self._balance = balance

Python не запрещает обратиться к _balance снаружи, но разработчик таким именем предупреждает: напрямую менять это значение не стоит.

Два подчеркивания __value

Двойное подчеркивание включает name mangling: Python изменяет имя атрибута внутри класса.

class Account:
    def __init__(self, balance):
        self.__balance = balance

Снаружи account.__balance не будет доступен напрямую. Это помогает защитить внутренние данные от случайного изменения.

Но важно помнить: в Python это не абсолютная безопасность, а механизм для контроля интерфейса класса.


@property, getter и setter

@property позволяет сделать метод похожим на обычный атрибут при чтении.

class Product:
    def __init__(self, name, price):
        self.name = name
        self._price = price
 
    @property
    def price(self):
        return self._price
 
product = Product("Клавиатура", 3500)
print(product.price)

price вызывается без скобок, хотя внутри это метод.

Чтобы контролировать запись значения, добавляют setter.

class Product:
    def __init__(self, name, price):
        self.name = name
        self._price = price
 
    @property
    def price(self):
        return self._price
 
    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError("Цена не может быть отрицательной")
        self._price = value
 
product = Product("Монитор", 18000)
product.price = 17000
print(product.price)

Getter должен возвращать значение через return. Setter обычно ничего не возвращает: его задача - проверить и записать данные.

Когда использовать property

@property полезен, когда нужно:

  • проверить значение перед записью;
  • сделать вычисляемое свойство;
  • скрыть внутреннюю реализацию;
  • сохранить простой интерфейс obj.attr, но добавить логику внутри;
  • не ломать внешний код, если внутренняя структура класса изменилась.

Типичные ошибки

Неправильное имя setter

class WrongExample:
    def __init__(self, value):
        self._value = value
 
    @property
    def value(self):
        return self._value
 
    @value.setter
    def set_value(self, new_value):  # ошибка: имя должно быть value
        self._value = new_value

Правильно:

class CorrectExample:
    def __init__(self, value):
        self._value = value
 
    @property
    def value(self):
        return self._value
 
    @value.setter
    def value(self, new_value):
        self._value = new_value

Слишком много параметров в setter

Setter принимает только self и одно новое значение.

class Point:
    def __init__(self, x, y):
        self._x = x
        self._y = y
 
    @property
    def coordinates(self):
        return self._x, self._y
 
    @coordinates.setter
    def coordinates(self, value):
        x, y = value
        self._x = x
        self._y = y

Setter без getter

Сначала создается getter через @property, и только потом можно добавить setter через @property_name.setter.


Практика

  • Задание 1: реестр курсов

    Создайте класс CourseRegistry с атрибутом класса courses. Реализуйте метод класса add_course, который добавляет новый курс, если его еще нет, и show_courses, который выводит список курсов.

    Подсказка: проверку на дубликаты лучше делать внутри classmethod.

  • Задание 2: альтернативный конструктор

    Создайте класс UserProfile, который принимает name и age. Добавьте метод класса from_birth_year(cls, name, birth_year), который вычисляет возраст и возвращает объект.

    Проверьте на значениях birth_year=1997 и birth_year=2003.

  • Задание 3: статические методы

    Создайте класс CurrencyConverter со статическими методами:

    • usd_to_eur(amount) по курсу 0.92;
    • eur_to_usd(amount) по курсу 1.09;
    • rub_to_usd(amount) по курсу 0.011.

    Методы не должны использовать self или cls.

  • Задание 4: скорость автомобиля

    Создайте класс Car со свойством speed.

    Требования:

    • скорость не может быть меньше 0;
    • скорость не может быть больше 280;
    • корректные значения сохраняются;
    • некорректные значения вызывают ValueError.
    car = Car(90)
    print(car.speed)
    car.speed = 140
    # car.speed = -20
    # car.speed = 320
  • Задание 5: исправить сломанный код

    Найдите и исправьте ошибки.

    class Temperature:
        def __init__(self, celsius):
            self._celsius = celsius
     
        @property
        def celsius(self):
            print(f"Текущая температура: {self._celsius}")
     
        @celsius.setter
        def set_celsius(self, value):
            if value < -273.15:
                raise ValueError("Ниже абсолютного нуля")
            self._celsius = value
     
        @property.setter
        def fahrenheit(self, value):
            self.celsius = (value - 32) * 5 / 9

    Что нужно проверить:

    1. getter должен возвращать значение;
    2. имя setter должно совпадать с именем свойства;
    3. нельзя создать setter без getter;
    4. для fahrenheit нужен корректный getter и setter.
  • Задание 6: вычисляемые свойства

    Создайте класс Rectangle с атрибутами width и height.

    Свойства:

    • area - только чтение;
    • perimeter - только чтение;
    • diagonal - только чтение;
    • dimensions - чтение и запись кортежа (width, height).

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

  • Задание 7: счет с историей

    Создайте класс Counter.

    Требования:

    • свойство value хранит текущее значение;
    • свойство history только для чтения и хранит историю значений;
    • свойство change_count показывает количество изменений;
    • одинаковое значение подряд не должно добавляться в историю.
  • Задание 8: банковский счет

    Создайте класс BankAccount с продвинутой валидацией.

    Атрибуты:

    • account_number - только для чтения после создания;
    • balance - не может быть отрицательным;
    • owner_name - непустая строка без цифр;
    • is_active - операции возможны только при активном счете.

    Дополнительно:

    • formatted_balance;
    • transaction_history;
    • account_info;
    • методы deposit(amount) и withdraw(amount).

Короткий итог

@classmethod работает с классом через cls, @staticmethod хранит связанную по смыслу утилитную функцию внутри класса, а @property позволяет добавить контроль чтения и записи атрибутов без усложнения внешнего интерфейса. Одинарное подчеркивание предупреждает о внутреннем атрибуте, двойное подчеркивание сильнее скрывает имя внутри класса.


⬅️ Назад: 03.05 - Наследование | Далее: 03.07 - Что такое импорт в Python ➡️ Модуль: 03 - MOC