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

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

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

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

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

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