Как exec работает с местными жителями?

Я думал, что это напечатает 3, но это печатает 1:

def f():
    a = 1
    exec("a = 3")
    print(a)

3 ответа

Решение

Эта проблема несколько обсуждается в списке ошибок Python3. В конечном итоге, чтобы получить такое поведение, вам нужно сделать:

def foo():
    ldict = {}
    exec("a=3",globals(),ldict)
    a = ldict['a']
    print(a)

И если вы проверите документацию Python3 на exec вы увидите следующее примечание:

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

Возвращаясь к конкретному сообщению в отчете об ошибке, Георг Брандл говорит:

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

Акцент мой.

Таким образом, суть в том, что Python3 может лучше оптимизировать использование локальных переменных, не допуская такого поведения по умолчанию.

И ради полноты, как упомянуто в комментариях выше, это работает как ожидалось в Python 2.X:

Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41) 
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
...     a = 1
...     exec "a=3"
...     print a
... 
>>> f()
3

Если вы находитесь внутри метода, вы можете сделать это:

class Thing():
    def __init__(self):
        exec('self.foo = 2')

x = Thing()
print(x.foo)

Вы можете прочитать больше об этом здесь

Причина, по которой вы не можете изменить локальные переменные внутри функции, используя exec таким образом, и почему exec Действует так, как он делает, можно резюмировать следующим образом:

  1. exec это функция, которая разделяет свою локальную область с областью самой внутренней области, в которой она вызывается.
  2. Всякий раз, когда вы определяете новый объект в области действия функции, он будет доступен в своем локальном пространстве имен, то есть он будет изменять local() толковый словарь. Когда вы определяете новый объект в exec то, что он делает, примерно эквивалентно следующему:

from copy import copy
class exec_type:
    def __init__(self, *args, **kwargs):
        # default initializations
        # ...
        self.temp = copy(locals())

    def __setitem__(self, key, value):
        if var not in locals():
            set_local(key, value)
        self.temp[key] = value

temp является временным пространством имен, которое сбрасывается после каждого создания экземпляра (каждый раз, когда вы вызываете exec).


  1. Python начинает искать имена из локального пространства имен. Это известно как манера LEGB. Python начинается с Local namespce, затем просматривает объемы Enclosing, затем Global и, в конце, ищет имена в пространстве имен Buit-in.

Более полным примером будет что-то вроде следующего:

g_var = 5

def test():
    l_var = 10
    print(locals())
    exec("print(locals())")
    exec("g_var = 222")
    exec("l_var = 111")
    exec("print(locals())")

    exec("l_var = 111; print(locals())")

    exec("print(locals())")
    print(locals())
    def inner():
        exec("print(locals())")
        exec("inner_var = 100")
        exec("print(locals())")
        exec("print([i for i in globals() if '__' not in i])")

    print("Inner function: ")
    inner()
    print("-------" * 3)
    return (g_var, l_var)

print(test())
exec("print(g_var)")

Выход:

{'l_var': 10}
{'l_var': 10}

местные жители одинаковы

{'l_var': 10, 'g_var': 222}

после добавления g_var и изменение l_var это только добавляет g_var и покинул l_var без изменений

{'l_var': 111, 'g_var': 222}

l_var изменен, потому что мы меняем и печатаем локальные данные в одном экземпляре (один вызов exec)

{'l_var': 10, 'g_var': 222}
{'l_var': 10, 'g_var': 222}

И в локальных функциях, и в локальных l_var неизменен и g_var добавлен

Inner function: 
{}
{'inner_var': 100}
{'inner_var': 100}

inner_functionлокальный такой же, как локальный exec

['g_var', 'test']

глобальный содержит только g_var и имя функции (после исключения специальных методов)

---------------------

(5, 10)
5
Другие вопросы по тегам