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

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

@python_quiz

Разберем этот квиз

Условие (код)

python

Короткий ответ

  • На экран будет выведено: [1]

Почему так происходит

  • В строке x = [] список объявлен как атрибут класса. Атрибуты класса доступны всем экземплярам и хранятся в самом классе, а не в отдельном экземпляре.
  • В методе add используется self.x.append(v). При обращении к self.x Python сначала проверяет атрибуты объекта; если атрибут не найден, берёт атрибут класса. Поскольку экземпляры не имеют собственного x, операция модифицирует общий список, принадлежащий классу.
  • В результате изменение, сделанное через a1, видно и через a2.

Демонстрация (выполнение)

python

Разбор вариантов ответа

  • "[]"

- Такой вывод был бы, если бы каждый экземпляр имел свой независимый список. Здесь это не так, потому что список объявлен на уровне класса.

  • "[1]"

- Соответствует действительности: общий список получил значение 1 через вызов a1.add(1).

  • "[1, 1]"

- Появился бы, если бы список добавлялся дважды (например, если бы add вызывался дважды) или если бы код где-то по ошибке дважды модифицировал общий список.

  • "Ошибка выполнения"

- Никакой ошибки не возникает: код корректен и выполняется.

Как избежать таких сюрпризов — правильные шаблоны

  1. Если нужно, чтобы каждый экземпляр имел свой список — инициализируйте его в конструкторе __init__:
python
  1. Если хотите оставить общую структуру, но при присвоении не мутировать общий объект (то есть создать отдельный список для экземпляра при первом изменении), можно указывать присвоение нового списка:
python
  1. Если классный список должен быть общим и мутируемым — документируйте это явно и будьте осторожны:
python

Рекомендации

  • Избегайте определения изменяемых структур (списков, словарей и т.п.) как атрибутов класса, если ожидаете независимые данные у экземпляров.
  • Если вам нужен shared state — используйте явную семантику (classmethods, статические методы, переменные класса) и документируйте поведение.
  • Для защиты данных можно возвращать копии: return list(self.x) или return self.x.copy().

Вывод

  • В приведённом примере список x общий для всех экземпляров класса, поэтому изменение через один экземпляр отражается во всех остальных. Частая и типичная ловушка для начинающих — хранить изменяемые объекты на уровне класса вместо конструктора.

Report Page