Создание динамически именованных переменных в функции в 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 в вашем источнике это не динамически установленное имя... отсюда и словари.