Классы и экземпляры 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__.
Когда атрибут класса удобнее атрибута экземпляра?
Когда значение логически общее для всех объектов, например неизменяемый код валюты или ограничение по умолчанию. Индивидуальные и изменяемые данные следует размещать на экземпляре.