Список в exec с пустыми локалями: NameError
Рассмотрим следующий фрагмент:
def bar():
return 1
print([bar() for _ in range(5)])
Он дает ожидаемый результат [1, 1, 1, 1, 1]
.
Однако, если я попытаюсь выполнить exec
тот же фрагмент в пустой среде (locals
и globals
оба установлены на {}
), он дает NameError
:
if 'bar' in globals() or 'bar' in locals():
del bar
# make sure we reset settings
exec("""
def bar():
return 1
print([bar() for _ in range(5)])
""", {}, {})
NameError: name 'bar' is not defined
Если я вызываю exec
как exec(…, {})
или exec(…)
, он выполняется как ожидалось.
Почему?
EDIT:
Рассмотрим также следующий фрагмент:
def foo():
def bar():
return 1
print('bar' in globals()) # False
print('bar' in locals()) # True
print(['bar' in locals() for _ in [1]]) # [False]
print([bar() for _ in [1, 2]]) # [1, 1]
Как и в моем первом exec, у нас нет бара у локальных жителей внутри понимания списка. Однако, если мы попытаемся вызвать его, он работает!
Ответы
Ответ 1
Решение вашей проблемы находится здесь:
Во всех случаях, если необязательные части опущены, код выполняется в текущей области. Если предусмотрены только глобальные значения, это должен быть словарь, который будет использоваться как для глобальных, так и для локальных переменных. Если указаны глобальные и локальные значения, они используются для глобальных и локальных переменных соответственно. Если это предусмотрено, локали могут быть любым объектом сопоставления. Помните, что на уровне модуля globals и locals являются одним и тем же словарем. Если exec получает два отдельных объекта в качестве глобальных и локальных, код будет выполнен, как если бы он был встроен в определение класса.
https://docs.python.org/3/library/functions.html#exec
В основном, ваша проблема заключается в том, что бар определяется в области locals
и только в locals
. Поэтому этот оператор exec()
работает:
exec("""
def bar():
return 1
print(bar())
""", {}, {})
Однако понимание списка создает новую локальную область, в которой bar
не определена и поэтому не может быть просмотрена.
Это поведение можно проиллюстрировать следующим образом:
exec("""
def bar():
return 1
print(bar())
print(locals())
print([locals() for _ in range(1)])
""", {}, {})
который возвращает
1
{'bar': <function bar at 0x108efde18>}
[{'_': 0, '.0': <range_iterator object at 0x108fa8780>}]
ИЗМЕНИТЬ
В вашем исходном примере определение bar
находится в глобальной области (уровне модуля). Это соответствует
Помните, что на уровне модуля глобальные и локальные языки являются одним и тем же словарем.
В примере exec
вы вводите искусственный раскол в области между глобальными и локальными, передавая два разных словаря. Если вы передали один или два глобала один (что в свою очередь означает, что этот будет использоваться как для globals
, так и locals
), ваш пример также будет работать.
Что касается примера, введенного в редактировании, это сводится к правилам определения области видимости в python. Подробное объяснение, пожалуйста, читайте: https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces
Короче говоря, в то время как bar
не входит в локальную область понимания списка и ни в глобальной области, ни в области foo. И учитывая правила обзора Python, если переменная не найдена в локальной области, она будет искать в охватывающих областях до тех пор, пока не будет достигнута глобальная область видимости. В вашем примере область foo находится между локальной областью и глобальной областью, поэтому до достижения цели будет отображаться строка.
Это, однако, по-прежнему отличается от примера exec, где область локалей, в которую вы проходите, не включает область понимания списка, но полностью разделена на нее.
Еще одно замечательное объяснение правил съемки, включая иллюстрации, можно найти здесь: http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html
Ответ 2
Как выяснил Хендрик Макайт, документация exec
гласит, что
Если exec
получает два отдельных объекта как globals
и locals
, код будет выполнен, как если бы он был встроен в определение класса.
Вы можете получить такое же поведение, введя код в определение класса:
class Foo:
def bar():
return 1
print([bar() for _ in range(5)])
Запустите его в Python 3, и вы получите
Traceback (most recent call last):
File "foo.py", line 9, in <module>
class Foo:
File "foo.py", line 15, in Foo
print({bar() for _ in range(5)})
File "foo.py", line 15, in <setcomp>
print({bar() for _ in range(5)})
NameError: global name 'bar' is not defined
Причиной ошибки является то, что Хендрик сказал, что для понимания списка создается новая неявная локальная область. Однако Python только когда-либо смотрит имена в 2 области: глобальные или локальные. Поскольку ни глобальная, ни новая локальная область не содержат имя bar
, вы получаете NameError
.
Код работает в Python 2, потому что в представлениях списков есть ошибка в Python 2 в том смысле, что они не создают новую область видимости, и, таким образом, они пропускают переменные в их текущую локальную область:
class Foo:
[1 for a in range(5)]
print(locals()['a'])
Запустите его в Python 2, а выход - 4
. Переменная a
теперь находится внутри локалей в классе класса и сохраняет значение из последней итерации. В Python 3 вы получите KeyError
.
Вы можете получить ту же ошибку в Python 2, хотя, если вы используете выражение генератора или понимание словаря/набора:
class Foo:
def bar():
return 1
print({bar() for _ in range(5)})
Ошибка может быть вызвана просто с помощью простого
class Foo:
bar = 42
class Bar:
print(bar)
Это не похоже на
def foo():
bar = 42
def baz():
print(bar)
baz()
потому что после выполнения foo
, Python делает baz
в закрытие, которое будет обращаться к переменной bar
с помощью специальной инструкции байт-кода.
Ответ 3
Я опаздываю на вечеринку здесь, но есть хорошая документация, похожая на модель исполнения.
В разделе 4.2.2 Разрешение имен:
Блоки и аргументы определения класса exec()
и eval()
являются особыми в контексте разрешения имен....
И затем в 4.2.4 Взаимодействие с динамическими функциями:
Функции eval()
и exec()
не имеют доступа к полной среде для разрешения имен. Имена могут быть разрешены в локальном и глобальном пространствах имен вызывающего. Свободные переменные не разрешаются в ближайшем охватывающем пространстве имен, а в глобальном пространстве имен. [1] Функции exec()
и eval()
имеют необязательные аргументы для переопределения глобального и локального пространств имен. Если указано только одно пространство имен, оно используется для обоих.
[1] Это ограничение происходит потому, что код, выполняемый этими операциями, недоступен в момент компиляции модуля.
Ответ 4
Изменить
Чтобы ответить на ваш отредактированный вопрос, пользователь @Hendrik Makait сказал, что bar
не входит в объем понимания списка:
def foo():
def bar():
return 1
print('bar' in globals()) # False, because the scope of foo and bar are diferents, foo is globals() scope, bar are in the scope of foo
print('bar' in locals()) # True
print(['bar' in locals() for _ in [1]]) # [False], because a new implicit scope is defined in list comprehension, as user @Antti Haapala said
print([bar() for _ in [1, 2]]) # [1, 1]
Чтобы ответить на исходный вопрос:
Если вы создаете два разных словаря, они не будут распознавать локальные и глобальные определения, переменные не обновляются, поскольку @PM 2Ring сказал:
exec("""
def bar():
return 1
print(bar())
print("bar" in globals())
print("bar" in locals())
print([bar() for _ in range(5)])
""", {},{})
он печатает:
1
False #not in globals
True
Traceback (most recent call last):
File "python", line 17, in <module>
File "<string>", line 7, in <module>
File "<string>", line 7, in <listcomp>
NameError: name 'bar' is not defined
Способ сделать это, обновляет переменные, такие как этот globals(). update (locals()):
exec("""
def bar():
return 1
globals().update(locals())
print("bar" in globals())
print("bar" in locals())
print([bar() for _ in range(5)])
""", {}, {})
который дает:
True
True
[1, 1, 1, 1, 1]
Но если вы удалите словари или создадите их и передадите их функции exec в качестве одного и того же параметра, это будет работать:
d={}
exec("""
def bar():
return 1
print("bar" in globals())
print("bar" in locals())
print([bar() for _ in range(5)])
""",d,d)
он печатает:
True
True
[1, 1, 1, 1, 1]
Вот почему вы получаете ошибку, она не может найти вашу функцию в глобальных переменных
Или просто, не указывайте параметры:
exec("""
def bar():
return 1
print(bar())
print("bar" in globals())
print("bar" in locals())
print([bar() for _ in range(5)])
""")
Вызывает тот же самый эффект.
Ответ 5
Здесь решение!
Нам нужно было получить локальное пространство имен после exec() для отслеживания изменений. Это не работает только с одним пространством имен, поэтому мы сделали это:
class MagickNameSpace(UserDict, dict):
"""A magic namespace for Python 3 exec().
We need separate global and local namespaces in exec(). This does not
work well in Python 3, because in Python 3 the enclosing namespaces are
not used to look up variables, which seems to be an optimization thing
as the exec'd code isn't available at module compilation.
So we make a MagickNameSpace that stores all new variables in a
separate dict, conforming to the local/enclosing namespace, but
looks up variables in both.
"""
def __init__(self, ns, *args, **kw):
UserDict.__init__(self, *args, **kw)
self.globals = ns
def __getitem__(self, key):
try:
return self.data[key]
except KeyError:
return self.globals[key]
def __contains__(self, key):
return key in self.data or key in self.globals
Замените старый код:
exec(code, global_ns, local_ns)
return local_ns
с:
ns = MagickNameSpace(global_ns)
ns.update(local_ns)
exec(code, ns)
return ns.data