Создание динамически именованных переменных в функции в python 3 / понимание exec / eval / locals в python 3
прежде всего, позвольте мне сказать, что я читал много потоков с аналогичными темами по созданию динамически именованных переменных, но они в основном относятся к Python 2 или предполагают, что вы работаете с классами. И да, я читал поведение функции exec в Python 2 и Python 3.
Я также знаю, что создание динамически именованных переменных-плохая идея в 99% случаев, и словари-это способ получить, но я просто хочу знать, возможно ли это и как именно exec и locals работают в python 3.
Я хотел бы показать немного примера кода, иллюстрирующего мой вопрос (Фибоначчи вычисляет числа Фибоначчи, 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, e.g. 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()
но переменные недоступны в функции.
Я был бы великолепен, если бы кто-то мог объясните это и скажите, намеренно ли это или это реальная непоследовательность в языке. Да, я знаю это!--16-->locals
Не следует изменять, но я не изменяю его, я вызываю exec()
...
2 ответов
когда вы не уверены, почему что-то работает так, как в 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()
(as exec
делает, если вы не передаете ему dict для использования в его пространстве имен).
действительно, давайте попробуем третью версию нашей функции, где мы заглянем в locals
опять же, когда у нас есть K
определяется как обычная локальная переменная:
def baz():
K = None
exec("K = 89")
print(locals())
вы не увидите 89
на выходе на этот раз либо!
>>> baz()
{"K": None}
причина, по которой вы видите старый K
значение locals()
объясняется функции:
обновить и вернуть словарь, представляющий текущую таблицу локальных символов.
слот, что локальная переменная K
значение S хранится в не было изменено exec
оператор, который только изменяет locals()
дикт. Когда вы звоните locals()
опять же, Python "update[s]" словарь со значением из слота, заменив значение, хранящееся там на exec
.
вот почему врачи говорят:
Примечание: содержимое этого словаря не должно изменяться; изменения не могут повлиять на значения локальных и свободных переменных, используемых интерпретатором.
код exec
вызов изменяет locals()
dict, и вы обнаруживаете, как его изменения не всегда видны в вашем более позднем коде.
на вопрос exec / eval / местные жители
по крайней мере, на реализации 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
в вашем источнике это не динамически установленное имя... отсюда и словари.