Python exec с цепочкой функций, создающей ошибку имени

#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 inside bar приводит def foo() к хранению foo в глобальных файлах вместо локальных, но это global объявление должно влиять только на присвоения foo inside bar .