Классы и экземпляры Python: состояние, методы и атрибуты

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

Где хранится состояние

Присваивание self.balance = 0 создаёт атрибут экземпляра. Атрибут, записанный непосредственно в теле класса, является общим атрибутом класса, пока конкретный экземпляр не перекроет его одноимённым значением. Не следует хранить изменяемый список уровня объекта в атрибуте класса: тогда разные экземпляры неожиданно разделят одну коллекцию.

Доступ account.deposit(100) связывает метод с account: Python автоматически передаёт этот объект как первый аргумент. Имя self является соглашением, но именно явный первый параметр делает источник состояния видимым в коде.

Рабочий пример: корзина с инвариантом

class ShoppingCart:
    currency = 'RUB'

    def __init__(self):
        self.items = []

    def add(self, title, price):
        if price < 0:
            raise ValueError('price must be non-negative')
        self.items.append((title, price))

    def total(self):
        return sum(price for _, price in self.items)


first = ShoppingCart()
second = ShoppingCart()
first.add('Книга', 900)
first.add('Курс', 1500)

print(first.total(), first.currency)
print(second.total(), second.items)

Ожидаемый результат:

2400 RUB
0 []

Каждый вызов конструктора создаёт новый items. Общая строковая константа currency безопасна как атрибут класса, а изменяемое содержимое корзины принадлежит экземпляру.

Пропущенный self и общее состояние

Если определить def add(title, price): ... без self, вызов через экземпляр передаст объект автоматически и может завершиться TypeError о лишнем позиционном аргументе. Сопоставьте сигнатуру метода с вызовом: первый параметр обычного метода принимает экземпляр, а пользовательские аргументы идут после него.

При загадочном обмене элементами между объектами найдите присваивание списка. Запись items = [] в теле класса создаёт одну общую коллекцию; перенесите её в __init__ как self.items = []. Для диагностики сравните first.items is second.items: у корректной реализации результат False. Также полезно смотреть vars(instance), чтобы увидеть собственные атрибуты конкретного объекта.

Тренировка: независимые списки чтения

Создайте класс ReadingList с атрибутом экземпляра books, методами add(title) и count(). Два экземпляра должны иметь независимые списки. Запретите пустое название через ValueError, предварительно удалив крайние пробелы. Для первого списка добавьте две книги, для второго одну и получите количества 2 и 1.

Самостоятельная проверка: временно перенесите books = [] на уровень класса, повторите сценарий и объясните общий результат через идентичность объектов. Затем восстановите создание списка в __init__, вызовите метод с пустой строкой и проверьте тип и текст исключения.

Состояние принадлежит экземпляру

Класс объединяет правила, экземпляр хранит конкретное состояние, а self указывает, с каким объектом работает метод. Изменяемые данные экземпляра обычно создают внутри __init__. Атрибуты класса подходят для действительно общих констант и настроек, но их совместное изменение должно быть осознанным.

Класс, экземпляр и конструктор

Обязан ли метод называться с параметром self?

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

Вызывает ли __init__ создание объекта?

Экземпляр создаёт __new__, после чего __init__ инициализирует его состояние. В большинстве прикладных классов достаточно определить только __init__.

Когда атрибут класса удобнее атрибута экземпляра?

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

Источники