Атрибуты в Python: экземпляр vs класс (setattr, delattr, getattr)

Атрибуты в Python: экземпляр vs класс (setattr, delattr, getattr)

@python_quiz

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

Короткая задача: что напечатает этот фрагмент кода и почему?

python

Быстрая суть

При доступе к атрибуту Python сначала проверяет атрибуты экземпляра, затем — атрибуты класса (и далее по иерархии наследования). Команды в примере меняют атрибуты в разных «местах», поэтому итоговый вывод определяется значением атрибута экземпляра.

Пошаговый разбор кода

  1. class C: x = 1

- Класс C получает атрибут x со значением 1. Это атрибут класса.

  1. c = C()

- Создаётся экземпляр c. Пока в c.__dict__ нет ключа 'x'.

  1. setattr(c, 'x', 2)

- Устанавливает атрибут 'x' прямо в экземпляре c (аналогично c.x = 2). Теперь у экземпляра есть собственный атрибут x: c.__dict__ содержит {'x': 2}.

  1. delattr(C, 'x')

- Удаляет атрибут x из класса C. После этого класс больше не имеет x в своём словаре атрибутов.

  1. getattr(c, 'x', 10)

- При попытке получить c.x сначала ищется в c.__dict__ — там есть значение 2, поэтому возвращается 2. Параметр по умолчанию 10 не используется.

Итог: программа выведет 2.

Демонстрация и дополнительные проверки

Ниже тот же пример с дополнительными выводами для проверки словарей атрибутов:

python

Ожидаемый вывод (сокращённо):

  • C.__dict__ до: {'x': 1}
  • c.__dict__ до: {}
  • После setattr: c.__dict__ содержит {'x': 2}
  • После delattr: класс больше не имеет 'x'
  • И, наконец: getattr(c, 'x', 10) -> 2

Разбор возможных альтернатив и типичных ошибок

  • Почему не 1? Значение 1 было в классе, но оно удалено через delattr(C, 'x'). Даже если бы классный атрибут остался, атрибут экземпляра (2) перекрывает значение класса.
  • Почему не 10? Параметр по умолчанию у getattr используется только если атрибут вообще не найден ни в экземпляре, ни в классе. Здесь атрибут найден в экземпляре.
  • Почему не AttributeError? AttributeError возник бы, если бы мы удалили атрибут и не передали значение по умолчанию в getattr (или делали бы прямой доступ c.x после удаления обоих мест хранения). В нашем случае getattr получает значение из экземпляра, поэтому исключения нет.

Небольшие подсказки для практики

  • Атрибуты экземпляра хранятся в экземпляре (обычно c.__dict__); они имеют приоритет перед атрибутами класса при чтении.
  • setattr/ delattr / getattr — это динамические аналоги операций присвоения, удаления и доступа (c.x = v, del C.x, c.x).
  • Если нужно удалить именно атрибут экземпляра, вызывайте delattr(c, 'x'); delattr(C, 'x') удалит атрибут у класса.

Вывод

В приведённом примере результат формируется тем, что экземпляру присвоили собственный атрибут со значением 2, а затем удалили атрибут у класса. При чтении атрибута возвращается значение экземпляра — 2.

Report Page