Поведение общих (классовых) атрибутов в Python

Поведение общих (классовых) атрибутов в Python

@python_quiz

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

Код из квиза:

python

Что здесь произойдет и почему? Кратко: на экран будет выведен список с одним элементом — ['admin'].

Ниже — подробное объяснение, разбор возможных вариантов и способы исправить (если нужно) такое поведение.

Почему вывод именно такой

  • Переменная tags определена на уровне класса (class User: tags = []). Это означает, что она принадлежит самому классу и является общей для всех экземпляров.
  • При обращении u1.tags и u2.tags Python сначала смотрит в словарь экземпляра, не находит атрибут, и возвращает атрибут класса. В обоих случаях это один и тот же список.
  • Вызов u1.tags.append('admin') изменяет этот общий список. Поэтому последующий print(u2.tags) покажет тот же изменённый список: ['admin'].

Анализ вариантов ответов (кратко)

  • Пустой список [] — не случится, потому что append изменил общий список.
  • ['admin'] — соответствует описанному поведению: оба экземпляра видят одно и то же изменение.
  • Ошибка при создании второго объекта — не происходит: создание второго экземпляра обычное и ошибок нет.
  • ['admin', 'admin'] — возможен только если append выполнится дважды или если код явно дублирует элемент; в данном фрагменте это не так.

Как исправить (если нужно, чтобы список был независимым у каждого экземпляра)

Обычно ожидаемое поведение — у каждого пользователя свой список тегов. Для этого определяйте атрибут в конструкторе:

python

Теперь u1.tags и u2.tags — разные списки.

Еще один прием — если вы хотите иметь дефолтные теги у класса, но позволить экземплярам иметь свои копии по умолчанию:

python

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

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

Выводы

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

Report Page