Объясните механизм: почему изменение словаря, возвращённого функцией locals(), не является надёжным способом изменить локальную переменную?
Изменение словаря, возвращённого locals(), не гарантирует изменения локальной переменной функции. В оптимизированной области видимости Python хранит локальные переменные во внутренних слотах выполнения, а словарь локальных имён служит главным образом для introspection — проверки состояния, а не для записи.
Python поддерживает динамические пространства имён, поэтому для модуля, класса и некоторых других контекстов естественно представлять имена словарём. Это удобно для отладки, выполнения динамического кода и инструментов анализа.
Однако локальные переменные функции особенно важны для производительности. Интерпретатор может хранить их не в обычном словаре, а в индексированных внутренних слотах. Поэтому универсальная семантика «изменил словарь — изменил переменную» для функций не гарантируется.
Разработчик может получить локальное пространство имён через locals(), изменить в нём значение и ожидать, что последующее обращение к локальному имени увидит это изменение. Такое решение хрупко: результат зависит от типа области видимости и деталей реализации или версии Python.
Ошибочное предположение особенно опасно в отладочных инструментах, загрузчиках конфигурации и динамическом создании переменных. Код может работать на уровне модуля, но не работать внутри функции, либо вести себя иначе после изменения реализации Python.
Внутри функции обращение к локальному имени обычно компилируется как доступ к локальному слоту, а не как поиск ключа в словаре. locals() предоставляет отображение текущих локальных имён, но запись в это отображение не обязана синхронно обновлять слоты, из которых выполняется обычный код функции.
В этом примере изменение словаря не следует считать способом переназначить value: обычное обращение к переменной и чтение из отображения могут дать разные результаты. Точное поведение представления локальных переменных зависит от области видимости и версии Python, поэтому полагаться на запись через locals() нельзя.
На уровне модуля имена обычно хранятся в словаре пространства имён модуля, поэтому изменение такого словаря может быть отражено при последующем поиске имени. Это не превращает locals() в общий механизм динамического присваивания: переносить такое поведение на функции нельзя.
Для обычного присваивания следует использовать явное имя. Если имена действительно нужно создавать динамически, безопаснее хранить их в отдельном словаре: он имеет ясную семантику данных и не зависит от внутренних правил хранения локальных переменных.
Инструмент запускает пользовательские вычисления внутри функции и хочет сохранить созданные значения для дальнейшего использования. Первый вариант — записывать результаты в словарь locals(). Его плюс — короткий интерфейс, но минус — отсутствие надёжной гарантии, что последующий код функции увидит добавленные имена.
Второй вариант — использовать exec с явно переданным словарём пространства имён. Это лучше отделяет динамические имена от локальных переменных, но усложняет контроль безопасности, отладку и статический анализ. Третий вариант — обычный словарь результатов, например context, куда вычисления явно записывают значения.
Практически выбирают отдельный словарь context: он предсказуем, тестируем и не зависит от оптимизации локальных переменных. locals() при этом оставляют для диагностики и чтения текущего состояния, а не используют как API изменения локального кода.
locals() одинаковое поведение во всех областях видимости?Нет. В функции локальные переменные могут обслуживаться оптимизированным механизмом, не совпадающим с изменяемым отображением. В модуле пространство имён непосредственно связано со словарём модуля, а в классе тело класса также формирует отдельное пространство имён. Поэтому область видимости нужно учитывать отдельно.
locals() стабильным словарем, который отражает все будущие изменения?Нет. Это представление или снимок текущего состояния, а не универсальный объект-ссылка на все операции присваивания. Даже если чтение показывает актуальные имена в конкретном месте, это не означает, что последующие присваивания или записи обратно будут синхронизированы во всех направлениях.
locals(), если имена действительно должны создаваться динамически?Следует использовать явно управляемое отображение, например словарь контекста. Тогда создание, чтение и удаление значений происходят через операции над данными, а не через неявную связь с компиляцией локальных переменных. Это повышает предсказуемость, облегчает тестирование и позволяет явно ограничить доступ к динамическим значениям.