Почему изменения в Python 3 для exec нарушают этот код? - PullRequest
11 голосов
/ 03 июля 2011

Я просмотрел множество тем 'Python exec' на SO, но не смог найти ни одного, который бы ответил на мою проблему.Ужасно извините, если об этом уже спрашивали.Вот моя проблема:

# Python 2.6: prints 'it is working'
# Python 3.1.2: "NameError: global name 'a_func' is not defined"
class Testing(object):
  def __init__(self):
    exec("""def a_func():
      print('it is working')""")
    a_func()

Testing()

# Python 2.6: prints 'it is working'
# Python 3.1.2: prints 'it is working'
class Testing(object):
  def __init__(self):
    def a_func():
      print('it is working')
    a_func()

Testing()

Поскольку определение стандартной функции работает в обеих версиях Python, я предполагаю, что проблема должна заключаться в изменении способа работы exec.Я прочитал документацию по API для 2.6 и 3 для exec, а также прочитал страницу «Что нового в Python 3.0» и не увидел причин, по которым код мог сломаться.

Ответы [ 2 ]

10 голосов
/ 03 июля 2011

Вы можете увидеть сгенерированный байт-код для каждой версии Python:

>>> from dis import dis

А для каждого переводчика:

#Python 3.2
>>> dis(Testing.__init__)
...
  5          10 LOAD_GLOBAL              1 (a_func)
...

#Python 2.7
>>> dis(Testing.__init__)
...
  5           8 LOAD_NAME                0 (a_func)
...

Как видите, Python 3.2 ищет глобальное значение (LOAD_GLOBAL) с именем a_func, а 2.7 сначала ищет локальную область (LOAD_NAME) перед поиском глобальной.

Если вы выполните print(locals()) после exec, вы увидите, что a_func создается внутри функции __init__.

Я действительно не знаю, почему это так, но, похоже, изменилось то, как таблицы символов обрабатываются.

Кстати, если вы хотите создать a_func = None поверх вашего __init__ метода, чтобы интерпретатор знал, что это локальная переменная, он не будет работать, поскольку байт-код теперь будет LOAD_FAST, а это не сделать поиск, но напрямую получает значение из списка.

Единственное решение, которое я вижу, - это добавить globals() в качестве второго аргумента к exec, чтобы создать a_func как глобальную функцию, к которой может получить доступ код операции LOAD_GLOBAL.

Редактировать

Если вы удалите оператор exec, Python2.7 изменит байт-код с LOAD_NAME на LOAD_GLOBAL. Поэтому, используя exec, ваш код всегда будет медленнее на Python2.x, потому что он должен искать изменения в локальной области.

Поскольку Python3 exec не является ключевым словом, интерпретатор не может быть уверен, что он действительно выполняет новый код или делает что-то еще ... Так что байт-код не меняется.

1044 * Е.Г. *

>>> exec = len
>>> exec([1,2,3])
3

Т.Л., др

exec('...', globals()) может решить проблему, если вам все равно, какой результат будет добавлен в глобальное пространство имен

6 голосов
/ 29 ноября 2012

Завершение ответа выше, на всякий случай.Если exec в какой-то функции, я бы рекомендовал использовать версию с тремя аргументами следующим образом:

def f():
    d = {}
    exec("def myfunc(): ...", globals(), d)
    d["myfunc"]()

Это самое чистое решение, поскольку оно не изменяет пространство имен под вашими ногами.Вместо этого myfunc сохраняется в явном словаре d.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...