Создание динамически названных переменных в функции в python 3 / Общие сведения о exec / eval / locals в python 3

Прежде всего, позвольте мне сказать, что я прочитал много потоков с похожими темами для создания динамически названных переменных, но они в основном относятся к Python 2 или предполагают, что вы работаете с классами. И да, я читал « Поведение функции exec» в Python 2 и Python 3 .

Я также знаю, что создание динамически названных переменных – плохая идея в 99% времени, и словари – это способ получить, но я просто хочу знать, возможно ли это, и как именно exec и locals работают в python 3.

Я хотел бы показать немного пример кода, иллюстрирующий мой вопрос (fibonacci вычисляет числа фибоначчи, ListOfLetters предоставляет ["A", "B", …]):

def functionname(): for index, buchstabe in enumerate(ListOfLetters.create_list("A", "K"), 1): exec("{} = {}".format(buchstabe, fibonacci(index)) ) #A = 1, B = 1, C = 2, D = 3, E = 5,... print(index, buchstabe, eval(buchstabe)) #works nicely, eg prints "4 D 3" print(locals()) #pritns all locals: {'B': 1, 'A': 1, 'index': 11, 'C': 2, 'H': 21, 'K': 89, ... print(locals()['K']) #prints 89 as it should print(eval("K")) #prints 89 as it should print(K) #NameError: name 'K' is not defined 

Поэтому, по крайней мере, при моем понимании, существует некоторая несогласованность в поведении locals() , поскольку в нем содержатся имена переменных, добавленные exec() но переменные недоступны в функции.

Я был бы великолепен, если бы кто-то мог объяснить это и сказать, это по дизайну или если это реальная несогласованность в языке. Да, я знаю, что locals не должны быть изменены, но я не изменяю их, я exec()

2 Solutions collect form web for “Создание динамически названных переменных в функции в python 3 / Общие сведения о exec / eval / locals в python 3”

Когда вы не знаете, почему что-то работает в Python, оно часто может помочь поместить поведение, которое вас путает в функции, а затем разобрать его из байт-кода Python с помощью этого модуля.

Давайте начнем с более простой версии вашего кода:

 def foo(): exec("K = 89") print(K) 

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

 >>> foo() Traceback (most recent call last): File "<pyshell#167>", line 1, in <module> foo() File "<pyshell#166>", line 3, in foo print(K) NameError: name 'K' is not defined 

Давайте разобраем его и посмотрим, почему:

 >>> import dis >>> dis.dis(foo) 2 0 LOAD_GLOBAL 0 (exec) 3 LOAD_CONST 1 ('K = 89') 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 9 POP_TOP 3 10 LOAD_GLOBAL 1 (print) 13 LOAD_GLOBAL 2 (K) 16 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 19 POP_TOP 20 LOAD_CONST 0 (None) 23 RETURN_VALUE 

Операция, на которую нужно обратить внимание, – это номер, обозначенный «13». Здесь компилятор обрабатывает поиск K в последней строке функции ( print(K) ). Он использует код операции LOAD_GLOBAL , который терпит неудачу, потому что «K» не является глобальным именем переменной, скорее это значение в нашем locals() dict (добавлено вызовом exec ).

Что, если мы убедим компилятор видеть K как локальную переменную (давая ей значение перед запуском exec ), поэтому он будет знать, что не искать глобальную переменную, которая не существует?

 def bar(): K = None exec("K = 89") print(K) 

Эта функция не даст вам ошибки, если вы ее запустите, но вы не получите ожидаемое значение:

 >>> bar() None 

Давайте разобрать, чтобы понять, почему:

 >>> dis.dis(bar) 2 0 LOAD_CONST 0 (None) 3 STORE_FAST 0 (K) 3 6 LOAD_GLOBAL 0 (exec) 9 LOAD_CONST 1 ('K = 89') 12 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 15 POP_TOP 4 16 LOAD_GLOBAL 1 (print) 19 LOAD_FAST 0 (K) 22 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 25 POP_TOP 26 LOAD_CONST 0 (None) 29 RETURN_VALUE 

Обратите внимание на коды операций, используемые в «3» и «19». Компилятор Python использует STORE_FAST и LOAD_FAST чтобы поместить значение для локальной переменной K в слот 0, а затем вернуть его обратно. Использование пронумерованных слотов значительно быстрее, чем вставка и выборка значений из словаря, такого как locals() , поэтому компилятор Python делает это для всего доступа к локальной переменной в функции. Вы не можете перезаписать локальную переменную в слоте, изменив словарь, возвращаемый locals() (как это делает exec , если вы не передадите ему dict для использования в своем пространстве имен).

Действительно, давайте попробуем третью версию нашей функции, где мы снова заглянем в locals когда мы определили K как обычную локальную переменную:

 def baz(): K = None exec("K = 89") print(locals()) 

На этот раз вы тоже не увидите 89 на выходе!

 >>> baz() {"K": None} 

Причина, по которой вы видите старое значение K в locals() , объясняется в документации функции :

Обновите и верните словарь, представляющий текущую локальную таблицу символов.

Слот, в котором хранится значение локальной переменной K не был изменен оператором exec , который только модифицирует locals() dict. Когда вы снова вызываете locals() , Python «обновляет [s]» словарь со значением из слота, заменяя значение, хранящееся там exec .

Вот почему в документах говорится:

Примечание . Содержимое этого словаря не должно изменяться; изменения могут не влиять на значения локальных и свободных переменных, используемых интерпретатором.

Ваш вызов exec изменяет locals() dict, и вы узнаете, как его изменения не всегда видны вашим более поздним кодом.

В вопросе exec / eval / locals

По крайней мере, при реализации модификации CPython в locals() словарь фактически не меняет имена в локальной области, поэтому он предназначен для использования только для чтения. Вы можете изменить его, и вы можете увидеть свои изменения в объекте словаря, но фактическая локальная область не изменяется.

exec() принимает два необязательных словарных аргумента, глобальную область видимости и локальную область. Он по умолчанию globals() и locals() , но поскольку изменения в locals() не являются «реальными» вне словаря, exec() влияет только на «реальную» локальную область, когда globals() is locals() , т.е. в модуле вне любой функции. (Так что в вашем случае это не работает, потому что это внутри области функций).

«Лучшим» способом использования exec() в этом случае является передача в ваш собственный словарь, а затем работа с значениями в этом.

 def foo(): exec_scope = {} exec("y = 2", exec_scope) print(exec_scope['y']) foo() 

В этом случае exec_scope используется как глобальная и локальная область для exec , а после exec он будет содержать {'y': 2, '__builtins__': __builtins__} (встроенные вставляются для вас, если нет)

Если вы хотите получить доступ к более глобальным значениям, вы можете сделать exec_scope = dict(globals()) .

Прохождение в разных словарях глобального и локального масштаба может привести к «интересному» поведению.

Если вы передаете один и тот же словарь в последовательные вызовы exec или eval , то они имеют одинаковую область действия, поэтому ваш eval работал (он неявно использовал словарь locals() ).

Имена динамических переменных

Если вы задаете имя из строки, что не так, чтобы получить значение в виде строки (то есть, что делает словарь)? Другими словами, почему вы хотите установить locals()['K'] а затем получить доступ к K ? Если K в вашем источнике, это не действительно динамически заданное имя … следовательно, словари.

  • Загрузка объекта JSON в Python с использованием модулей urllib.request и json
  • Проблемы с редактированием поля многостраничного изображения
  • Какой ключ не удалось выполнить в Python KeyError?
  • «Объекты TypeError: DataFrame изменяемы, поэтому они не могут быть хэшированы» при сортировке индекса dataframe pandas
  • Python AttributeError при вызове nltk Brown corpus
  • карта в python2 vs python3
  • TypeError: получил несколько значений для аргумента
  • Python: добавление к dict одного объекта в списке изменяет все dicts каждого другого объекта в списке
  • Python - лучший язык программирования в мире.