Создание динамически названных переменных в функции в python 3 / Общие сведения о exec / eval / locals в python 3
Прежде всего, позвольте мне сказать, что я прочитал много потоков с похожими темами для создания динамически названных переменных, но они в основном относятся к Python 2 или предполагают, что вы работаете с классами. И да, я читал « Поведение функции exec» в Python 2 и Python 3 .
Я также знаю, что создание динамически названных переменных – плохая идея в 99% времени, и словари – это способ получить, но я просто хочу знать, возможно ли это, и как именно exec и locals работают в python 3.
- Слушайте нажатие клавиши с асинхронным нажатием
- Python приостанавливает пока истинный цикл до выполнения условия
- Связь между родительскими дочерними процессами
- Псевдонимы для команд с CMD-модулем Python
- Как использовать переменную внутри регулярного выражения?
Я хотел бы показать немного пример кода, иллюстрирующий мой вопрос (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()
…
- прерывание клавиатуры с помощью python gtk?
- argparse необязательное значение для аргумента
- Параметры интервала Tkinter.grid?
- Как установить pip3 в Windows?
- Отформатируйте строку, в которой есть дополнительные фигурные скобки
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
в вашем источнике, это не действительно динамически заданное имя … следовательно, словари.
- Функция калькулятора не выводит ничего
- Почему назначение Python не возвращает значение?
- Есть ли API данных Google (gdata) для Python 3.x?
- ТипError: метод () принимает 1 позиционный аргумент, но 2 даны
- Поведение округления Python 3.x
- Каковы правила определения контекста списка в классе Python?
- «Объекты TypeError: DataFrame изменяемы, поэтому они не могут быть хэшированы» при сортировке индекса dataframe pandas
- Ошибка компиляции Matplotlib: TypeError: unorderable types: str () <int ()
- Ошибка Python3: initial_value должно быть str или None
- Как сделать таблицу на Python 3.5.2?
- Редактирование csv-файла для редактирования подсетей, чтобы не было перекрытия в диапазонах IP (модуль ipaddress в Python 3.3)
- Python - Selenium - Как использовать ярлыки браузера