2025, Sep 28 23:15

Динамический доступ к атрибутам в Python: setattr, getattr и __dict__

Разбираем, почему instance.tag не работает в цикле, и как корректно читать динамические атрибуты в Python через getattr и __dict__. Примеры кода и советы.

Динамический доступ к атрибутам в Python кажется простым, пока не пытаешься прочитать эти атрибуты внутри цикла. Запись obj.i в теле цикла не извлечёт значения для a, b или c — в этом и загвоздка. Задача — пройтись по данным, создать атрибуты на их основе и в том же проходе корректно их прочитать.

Постановка задачи

Рассмотрим минимальный шаблон, где атрибуты добавляются во время выполнения, а затем читаются:

instance = DemoType()

labels = ['a', 'b', 'c']

for tag in labels:
  attr_id = f"{tag}"
  setattr(instance, attr_id, [f"{tag}"])
  print(instance.tag)

Ожидается, что вы увидите значения для instance.a, instance.b и instance.c — каждое в виде списка из одного элемента. Но последняя строка буквально обращается к instance.tag, а такого атрибута мы не создавали.

Что происходит на самом деле

Цикл действительно создаёт атрибуты, имена которых формируются из текущего значения итерации. Однако обращение instance.tag не связано с переменной tag — интерпретатор ищет атрибут с именем ровно "tag". Поэтому попытка прочитать только что созданные атрибуты для "a", "b" и "c" не срабатывает. Чтобы получать атрибуты по вычисляемому имени, нужно делать поиск по имени.

Решение

Атрибуты обычного объекта Python доступны как словарь через instance.__dict__. Если проиндексировать это отображение именем, вычисленным в цикле, вы получите только что записанное значение. Так можно и вывести его, и собрать список для проверки.

instance = DemoType()

labels = ['a', 'b', 'c']

for tag in labels:
  attr_id = f"{tag}"
  setattr(instance, attr_id, [f"{tag}"])
  print(instance.__dict__[attr_id])

Если удобнее получать значение через встроенную функцию, симметричную setattr, используйте getattr с тем же вычисленным именем:

instance = DemoType()

labels = ['a', 'b', 'c']

for tag in labels:
  attr_id = f"{tag}"
  setattr(instance, attr_id, [f"{tag}"])
  print(getattr(instance, attr_id))

Чтобы собрать значения в список для проверки, пройдитесь по тем же ключам и считайте их по имени:

instance = DemoType()

labels = ['a', 'b', 'c']

for tag in labels:
  attr_id = f"{tag}"
  setattr(instance, attr_id, [f"{tag}"])

values_snapshot = [instance.__dict__[name] for name in labels]
print(values_snapshot)

Почему это важно

При динамическом создании атрибутов прямой доступ через точку со статичным идентификатором не учитывает переменную, вычисленную в цикле. Доступ по имени сохраняет согласованность: записали по имени — читайте по имени. Во многих случаях удобнее завести внутри класса отдельное «хранилище свойств» на базе dict, а иногда и вовсе использовать обычный словарь вместо атрибутов — это проще и нагляднее.

Итоги

Если атрибуты создаются через setattr, считывайте их по вычисленному имени. Доступ через instance.__dict__[name] или getattr(instance, name) симметричен записи и предотвращает неожиданные эффекты. Если по сути вам нужен key-value, присмотритесь к решению на базе словаря — API получится явным, а структура данных простой.

Статья основана на вопросе с StackOverflow от Javier Olivares и ответе от Richard.