Конкретная причина этой проблемы заключается в том, как вы вызывали exec
внутри функции.Когда exec
используется в форме с одним аргументом, он использует локальные параметры вызывающего контекста.Это хорошо для модулей и классов, но усложняется внутри функции.exec
работает с локальными переменными, которые хранятся в dict
(как и модули и классы).Однако локальные функции хранятся по-разному [1].Таким образом, exec
должен создать представление словаря для локальных функций и работать с ним.Это означает, однако, что любые изменения в этом словаре не отражаются в фактических локальных значениях функции.
def f():
# NB. do not rely on any behaviour that results from mutating the dict returned by locals
x = 'value'
assert locals()['x'] == 'value'
assert 'builtins' not in locals()
exec('import builtins')
try:
builtins
except NameError as e:
print(e)
else:
assert False
print(locals()['builtins'])
# prints
# name 'builtins' is not defined
# <module 'builtins' (built-in)>
Правильное решение в вашем случае дано bruno desthuilliers.Однако, чтобы exec
и eval
работали надежным образом, вы должны предоставить глобальные и локальные данные для их использования, где это возможно.Это могут быть любые словари, которые вы хотите, а не только глобальные или локальные элементы вашего контекста.
например.
def g(module_name, attr):
locals_ = {}
globals_ = {}
# two-arg form where locals and globals are the same
exec('import ' + module_name, globals_)
assert module_name not in locals_
assert module_name in globals_
exec('result = {}.{}'.format(module_name, attr), globals_, locals_)
assert 'result' in locals_
assert 'result' not in globals_
return eval('{}.{}'.format(module_name, attr), globals_)
assert g('builtins', 'str') is str
[1] Они хранятся в массиве фиксированной длины, иИндекс для каждой локальной переменной вычисляется во время компиляции функции.Функции не имеют переменных локальных переменных - у них нет места для их хранения и нет способа узнать, как их получить.