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

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

@python_quiz

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

Короткий пример показывает класс с атрибутом, который является изменяемым объектом (списком). Вопрос — что будет напечатано при создании двух экземпляров и модификации списка через первый экземпляр?

Исходный код и поведение

python

При выполнении этого кода в консоль попадёт:

Почему так происходит — разберём ниже.

Пояснение: что такое атрибут класса

  • Атрибут, объявленный прямо в теле класса (вне методов), является атрибутом класса.
  • Для неизменяемых типов это обычно безопасно, но для изменяемых (list, dict, set и т. п.) объект создаётся один раз при определении класса и разделяется всеми экземплярами.
  • В примере flags — один список, общий для всех объектов Config. Когда c1.flags.append('A') модифицирует список, изменение видно у всех экземпляров, включая c2.

Для наглядности можно вывести идентификаторы объектов:

python

Анализ возможных вариантов (кратко)

  • Будет выведено [] — неверно: список уже получил 'A' через c1.
  • Будет выведено ['A'] — соответствует поведению, т.к. список — атрибут класса, общий для экземпляров.
  • Будет ошибка при создании второго экземпляра — неверно: создание второго экземпляра проходит без ошибок.
  • Будет выведено ['A', 'A'] — неверно для данного кода (получилось бы, если бы и c1, и c2 по очереди добавляли по 'A').

Как избежать такого поведения (обычные паттерны)

Если нужно, чтобы каждый экземпляр имел свой собственный список, инициализируйте его в конструкторе:

python

Или используйте dataclasses с default_factory:

python

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

Когда общий атрибут полезен

Иногда намеренно нужен общий для всех экземпляров ресурс — например, кэш, счётчик, регистрация всех созданных объектов. Тогда класс-атрибут — правильный выбор. Важно лишь осознавать, что объект разделяется всеми экземплярами.

Итог

Мутируемые объекты, заданные как атрибуты класса, создаются один раз и разделяются между всеми экземплярами. Если нужно поведение "каждому экземпляру — свой объект", нужно инициализировать атрибут в __init__ или использовать default_factory в dataclass. Это распространённая ловушка для тех, кто знаком с функциями и параметрами по умолчанию — принцип тот же.

Report Page