#python #python-3.x #python-exec
#python #python-3.x #python-exec
Вопрос:
Рассмотрим следующий скрипт, который использует exec
для определения двух функций, одна из которых вызывает другую:
def run_code():
code = """
def foo():
print('foo')
return 1
def bar():
print('bar calls foo')
return 1 foo()
result = bar()
"""
exec(code, globals(), locals())
print('Result: {}'.format(locals()['result']))
run_code()
Я ожидал бы увидеть следующий вывод:
bar calls foo
foo
Result: 2
но вместо этого я получаю следующий вывод трассировка стека:
bar calls foo
Traceback (most recent call last):
File "minimal.py", line 17, in <module>
run_code()
File "minimal.py", line 14, in run_code
exec(code, globals(), locals())
File "<string>", line 10, in <module>
File "<string>", line 8, in bar
NameError: name 'foo' is not defined
Интересно, что если содержимое run_code
перемещается на уровень модуля, то оно работает нормально. Однако, если я затем заменю globals()
or locals()
новым пустым словарем, он снова сломается. Я также знаю, что помещение def foo
inside bar
в тело заставит его работать.
Почему возникает эта ошибка и как ее правильно исправить?
(Я знаю, что exec
это обычно не одобряется. Я использую его по уважительной причине.)
Комментарии:
1. Изменение
locals()
— это неопределенное поведение, и вы делаете это здесь.2. @user2357112supportsMonica Если я заменю
locals()
иglobals()
новыми пустыми словарями, проблема не исчезнет.
Ответ №1:
Из документации:
Если указано, локальными объектами может быть любой объект сопоставления. Помните, что на уровне модуля глобальные и локальные значения являются одним и тем же словарем. Если exec получает два отдельных объекта как глобальные и локальные, код будет выполняться так, как если бы он был встроен в определение класса.
И определения классов не создают объемлющую область, обратите внимание, вот почему вы не можете вызвать метод из другого метода без использования self
. Так что просто передайте globals()
словарь. Или передайте два одинаковых dict для обоих аргументов.
In [4]: def run_code():
...: code = """
...: def foo():
...: print('foo')
...: return 1
...:
...: def bar():
...: print('bar calls foo')
...: return 1 foo()
...:
...: result = bar()
...: """
...: namespace = {}
...: exec(code, namespace)
...: print('Result: {}'.format(namespace['result']))
...:
In [5]: run_code()
bar calls foo
foo
Result: 2
Ответ №2:
code = """
def foo():
print('foo')
return 1
def bar():
global foo;
print('bar calls foo')
return 1 foo()
result = bar()
"""
def run_code():
exec(code, globals(), locals())
print('Result: {}'.format(locals()['result']))
run_code()
Вывод:
bar calls foo
foo
Result: 2
Комментарии:
1. подождите, какого черта —
global
так работать не должно.2. Я думаю, что это на самом деле ошибка.
global foo
insidebar
приводитdef foo()
к хранениюfoo
в глобальных файлах вместо локальных, но этоglobal
объявление должно влиять только на присвоенияfoo
insidebar
.