Можно ли получить доступ к exec-предоставленному глобальному словарю из функции?

Можно ли получить доступ к предоставленному exec глобальному словарю изнутри функции, если функция была определена вне exec-ed кода (и, таким образом, уже привязана к другому __globals__)?

Другими словами, есть ли способ заставить следующий пример работать?

def f():
    log("Hi")

exec('f()', {'f': f, 'log': print})

В общем, можно ли заменить __globals__ функции?

2 ответа

Решение

Это довольно странная вещь, но она выполнима.

Ваш exec вызов выполняет оператор f() в предоставленных глобалах. Он не выполняет тело f в предоставленных глобалах. Предоставленные глобальные переменные используются в неправильном кадре стека. Чтобы получить доступ к этим глобальным f Вы можете использовать проверку стека:

import inspect

def f():
    log = inspect.currentframe().f_back.f_globals['log']
    log('Hi')

exec('f()', {'f': f, 'log': print})

Если вы хотите выполнить тело f с предоставленными глобальными файлами, а не просто с получением доступа к глобальным, вам необходимо сделать копию f с вашими собственными глобальными переменными:

import types
my_f = types.FunctionType(f.__code__,
                          {'log': print},
                          f.__name__,
                          f.__defaults__,
                          f.__closure__)
my_f()

Конструктор типа функции в некотором роде задокументирован; его нет в онлайновых документах, но он задокументирован в строке документации типа функции:

function(code, globals[, name[, argdefs[, closure]]])

Create a function object from a code object and a dictionary.
The optional name string overrides the name from the code object.
The optional argdefs tuple specifies the default argument values.
The optional closure tuple supplies the bindings for free variables.

Не уверен, что я полностью прав насчет объяснения. Короче говоря, пример не может работать в Python 3.

Причина в сочетании двух обстоятельств: [1] - exec это функция в Python 3, [2] - код, который вы пытаетесь выполнить, содержит вызов функции.

Когда вы предоставляете globals необязательный аргумент функции exec это локальный охват этой самой функции. Так работает следующий пример:

exec('log("Hi")', {'log': print})

Но оригинал не делает. Потому что в исходном примере вы вызываете функцию f, У него есть свой локальный охват. Что делает Python? Он проверяет глобальную область (действительную глобальную область программы) и самую внутреннюю область (локальную область функции f). Обе области не хватает для log и вы получите NameError,

Вы можете получить то же самое поведение (ту же ошибку) с двумя обычными функциями:

def f():
    log("Hi")


def f_():
    log = print
    f()

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