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

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

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

Ответы

Ответ 1

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

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

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

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

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

Изменение локалей функции "на лету" невозможно без нескольких последствий: обычно локаторы функций не хранятся в словаре, а массив, индексы которого определяются во время компиляции из известных локалей. Это сталкивается, по крайней мере, с новыми локалями, добавленными exec. Старая инструкция exec обошла это, потому что компилятор знал, что если в функции произошел exec без globals/locals args, это пространство имен будет "неоптимизировано", то есть не будет использовать массив 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

Ответ 3

Причину того, что вы не можете изменить локальные переменные внутри функции, использующей 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 изменен, потому что мы меняем и l_var в одном экземпляре (один вызов exec).

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

В обеих функциях locals и exec local l_var не изменяется и g_var.

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

inner_function local - это то же самое, что exec local.

['g_var', 'test']

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

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

(5, 10)
5