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

Использование функции, определенной в строке exec в Python 3

Почему следующий код python3 вызывает ошибку?

a='''
def x():
  print(42)
'''

class Test:
    def __init__(self):
        exec(a)
        x()

t = Test()

Результаты в этом сообщении:

Traceback (most recent call last):
  File "bug.py", line 11, in <module>
    t = Test()
  File "bug.py", line 9, in __init__
    x()
NameError: global name 'x' is not defined
4b9b3361

Ответ 1

Примечание. exec - это просто оператор Simple в Python 2.x, тогда как это функция в Python 3.x.

Python 2.7

Давайте проверим изменения, сделанные при выполнении a.

class Test:
    def __init__(self):
        l, g = locals().copy(), globals().copy()
        exec a           # NOT a function call but a statement
        print locals() == l, globals() == g
        x()

t = Test()

Выход

False True
42

Это означает, что он что-то изменил в словаре locals. Если вы печатаете locals().keys() до и после exec, вы увидите x, после exec. Согласно документации exex,

Во всех случаях, если необязательные части опущены, код выполняется в текущей области.

Таким образом, он делает именно то, что говорит документация.

Python 3.x:

Когда мы выполняем то же самое в Python 3.x, мы получаем аналогичный результат, за исключением того, что получаем эту ошибку.

class Test:
    def __init__(self):
        l, g = locals().copy(), globals().copy()
        exec(a)          # Function call, NOT a statement
        print(locals() == l, globals() == g)
        x()

Выход

False True
NameError: name 'x' is not defined

Даже документация функции exec говорит,

Во всех случаях, если необязательные части опущены, код выполняется в текущей области.

Но он также содержит примечание внизу,

Примечание. Локальные сети по умолчанию действуют, как описано для функции locals() ниже: изменения в локальном каталоге по умолчанию не должны выполняться. Передайте явный словарь locals, если вам нужно увидеть эффекты кода на локалях после возврата функции exec().

Итак, мы с любопытством проверяем документацию locals() и находим

Примечание: Содержимое этого словаря не должно изменяться; изменения могут не влиять на значения локальных и свободных переменных, используемых интерпретатором.

Таким образом, интерпретатор не соблюдает изменения, внесенные в объект locals(). Вот почему он не распознает x, как определено в локальной области.

Но когда мы делаем

def __init__(self):
    exec(a, globals())
    x()

он работает, потому что мы добавляем его в словарь globals. Сначала Python пытается найти x в локальной области, а затем в области класса, а затем в глобальной области действия и находит ее там. Поэтому он без проблем запускает его.

Ответ 2

Я предполагаю, что вы используете Python3.x, поскольку в Python2.7 ваш код работает отлично для меня. Итак, для Python3.x измените строку

exec(a)

to

exec(a, globals())

чтобы добавить x в глобальное пространство имен.

Документация

Ответ 3

Python3 exec также принимает необязательные аргументы типа отображения globals и locals, которые действуют как контекст для данного кода выполнение:

exec(object[, globals[, locals]])

По умолчанию локальная область пропускается для обоих. Выполненный код может использовать его, также может изменять dict, но он не будет влиять на реальную локальную область. См. locals() и пример:

a = '''
print(t)

def x():
    print(42)
'''

class Test:
    def __init__(self):
        t = 'locals are accessible'
        # same as calling exec(a, locals())
        exec(a)
        print(locals())
        x()

t = Test()

Вывод:

locals are accessible
{'x': <function x at 0x6ffffd09af0>,
'self': <__main__.Test object at 0x6ffffce3f90>,
't': 'locals are accessible'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 13, in __init__
NameError: global name 'x' is not defined

Если вы хотите, чтобы x был доступен после вызова exec, вам необходимо передать глобальную или настраиваемую область:

# global scope
class Test:
    def __init__(self):
        exec(a, globals())
        x()

# custom scope
class Test:
    def __init__(self):
        scope = {}
        exec(a, scope)
        scope['x']()