Как 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
Действует так, как он делает, можно резюмировать следующим образом:
exec
это функция, которая разделяет свою локальную область с областью самой внутренней области, в которой она вызывается.- Всякий раз, когда вы определяете новый объект в области действия функции, он будет доступен в своем локальном пространстве имен, то есть он будет изменять
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
).
- 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