Подтвердить что ты не робот

Почему Python 3 изменился на exec, нарушив этот код?

Я просмотрел потоки Myriad "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" и не видел никакой причины, по которой код сломался.

4b9b3361

Ответ 1

Вы можете увидеть сгенерированный байт-код для каждой версии 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__.

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

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

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

Edit

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

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

например.

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

TL;DR

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

Ответ 2

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

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

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