Список в 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