Практическая ситуация: два экземпляра класса неожиданно видят изменения одного изменяемого атрибута; какой механизм Python это объясняет?
Изменяемый атрибут, заданный в теле класса, хранится в самом объекте класса и используется всеми экземплярами, пока экземпляр не получит собственное значение с тем же именем. Поэтому изменение объекта через один экземпляр видно через другой: меняется общий объект, а не ссылка только одного экземпляра.
Модель атрибутов Python разделяет данные класса и данные экземпляра. Атрибуты класса предназначены для общих настроек, констант и методов, а экземплярные атрибуты — для состояния конкретного объекта.
Такой подход позволяет не дублировать общие значения в каждом экземпляре. Обратная сторона — изменяемый объект класса может стать непреднамеренным общим состоянием.
При обращении к атрибуту экземпляра Python сначала ищет подходящее значение в самом экземпляре, затем в его классе и далее в базовых классах. Если в экземпляре собственного атрибута нет, используется найденный объект класса.
Операция изменения объекта, например добавление элемента в общий список, не создаёт атрибут экземпляра. В результате состояние одного объекта влияет на остальные, что особенно опасно в обработчиках запросов, кэшах и накопителях данных.
Ключевое различие — между изменением объекта и присваиванием имени. Если экземпляр получил список из класса, вызов операции изменения списка меняет сам общий список. Если же выполнить присваивание атрибуту экземпляра, Python создаст в экземпляре отдельное имя, скрывающее одноимённый атрибут класса.
В первом случае оба обращения находят один список в Basket.__dict__. После присваивания first.items появляется отдельный атрибут в first.__dict__; second.items по-прежнему разрешается через класс.
Безопасный вариант — создавать изменяемое значение отдельно для каждого экземпляра, обычно в __init__. Для неизменяемых значений атрибут класса часто безопасен, поскольку операция вроде присваивания числа экземпляру создаёт отдельное экземплярное имя.
Следует учитывать наследование: если подкласс не переопределил атрибут, он также использует объект из базового класса. Изменение этого объекта будет видно экземплярам обоих классов. При проектировании API нужно явно решить, является ли состояние общим или принадлежащим экземпляру.
В классе клиента разработчик разместил словарь headers на уровне класса, чтобы задать значения по умолчанию. Один экземпляр добавил в него токен авторизации, после чего токен начали получать другие экземпляры.
Можно было копировать словарь перед каждым использованием. Это уменьшает риск случайного изменения общего состояния, но требует дисциплины и не решает проблему, если сам экземпляр должен владеть изменениями.
Выбранное решение — создавать новый словарь в конструкторе для каждого клиента, используя общие неизменяемые настройки класса только как исходные значения. В результате экземпляры получили независимое состояние, а общие настройки остались централизованными.
Ответ: Присваивание создаст атрибут в словаре конкретного экземпляра и не изменит атрибут класса. После этого данный экземпляр будет находить собственное значение первым, а остальные экземпляры продолжат использовать значение класса.
Ответ: Изменяемая операция над объектом класса может затронуть его непосредственно. Для неизменяемого объекта операция изменения невозможна: новое значение создаётся заново, а присваивание имени экземпляру обычно формирует собственный атрибут. Однако вложенный изменяемый объект всё равно может разделяться, например список внутри кортежа.
Ответ: Нужно создавать его на уровне экземпляра, например в __init__, а не полагаться на атрибут класса. Если состояние должно отличаться для каждого подкласса, его можно переопределить отдельным объектом в каждом классе, но это всё равно будет общим состоянием внутри соответствующего класса, а не каждого экземпляра.