Подтвердить что ты не робот

C Python: запуск кода Python в контексте

Функция API Python C PyEval_EvalCode позволяет выполнять скомпилированный код Python. Я хочу выполнить блок кода Python , как если бы он выполнялся в рамках функции, поэтому он имеет свой собственный словарь локальных переменных, которые не влияют на глобальное состояние.

Это кажется достаточно простым, поскольку PyEval_EvalCode позволяет вам предоставить глобальный и локальный словарь:

PyObject* PyEval_EvalCode(PyCodeObject *co, PyObject *globals, PyObject *locals)

Проблема, с которой я сталкиваюсь, связана с тем, как Python ищет имена переменных. Рассмотрим следующий код, который я выполняю с помощью PyEval_EvalCode:

myvar = 300
def func():
    return myvar

func()

Этот простой код действительно вызывает ошибку, поскольку Python не может найти переменную myvar из func. Даже если myvar находится в локальном словаре во внешней области, Python не копирует его в локальный словарь во внутренней области. Причина этого заключается в следующем:

Всякий раз, когда Python ищет имя переменной, сначала он проверяет locals, затем проверяет globals и, наконец, проверяет builtins. В области модуля locals и globals являются САМЫМ словарным объектом. Таким образом, оператор x = 5 в области модуля поместит x в словарь locals, который также является словарем globals. Теперь функция, определенная в области модуля, которая нуждается в поиске x, не найдет x внутри функции-области locals, потому что Python не копирует локали области модуля в локальные области функций. Но это обычно не проблема, потому что он может найти x в globals.

x = 5
def foo():
   print(x) # This works because 'x' in globals() == True

Это только с вложенными функциями, что Python, похоже, копирует локальные локальные объекты во внутренние локали. (Это также кажется ленивым, только если они необходимы во внутренней области.)

def foo():
   x = 5
   def bar():
      print(x) # Now 'x' in locals() == True
   bar()


Таким образом, результатом всего этого является то, что при выполнении кода в области модуля вы должны убедиться, что ваш глобальный словарь и локальный словарь являются ТОЛЬКО объектом, иначе функции области видимости модуля не смогут получить доступ к переменным области видимости модуля.

Но в моем случае я не хочу, чтобы глобальный словарь и локальный словарь были одинаковыми. Поэтому мне нужно каким-то образом сказать интерпретатору Python, что я выполняю код в области функций. Есть ли способ сделать это? Я просмотрел PyCompileFlags, а также дополнительные аргументы PyEval_EvalCodeEx и не могу найти способ сделать это.

4b9b3361

Ответ 1

Python фактически не копирует локальные локаторы внешней видимости во внутренние локальные области; в документации для locals указано:

Свободные переменные возвращаются locals(), когда они вызываются в функциональных блоках, но не в блоках классов.

Здесь "свободные" переменные относятся к переменным, закрытым вложенной функцией. Это важное различие.

Самое простое решение для вашей ситуации - просто передать один и тот же объект dict как globals и locals:

code = """
myvar = 300
def func():
    return myvar

func()
"""
d = {}
eval(compile(code, "<str>", "exec"), d, d)

В противном случае вы можете обернуть свой код в функцию и извлечь его из скомпилированного объекта:

s = 'def outer():\n    ' + '\n    '.join(code.strip().split('\n'))
exec(compile(s, '<str>', 'exec').co_consts[0], {}, {})