Проблемы с пространством имен при вызове patsy внутри функции


Я пытаюсь написать оболочку для API формулы statsmodels (это упрощенная версия, функция делает больше, чем это):

import statsmodels.formula.api as smf

def wrapper(formula, data, **kwargs):
    return smf.logit(formula, data).fit(**kwargs)

Если я дам эту функцию пользователю, который затем попытается определить свою собственную функцию:

def square(x):
    return x**2

model = wrapper('y ~ x + square(x)', data=df)

Они получат NameError, потому что модуль patsy ищет в пространстве имен wrapper функцию square. Есть ли безопасный, Питонский способ справиться с этой ситуацией, не зная априори, что такое имена функций или сколько их функции будут необходимы?

К вашему сведению: это для Python 3.4.3.

2 3

2 ответа:

Statsmodels использует пакет patsy для анализа формул и создания матрицы проектирования. patsy разрешает пользовательские функции как часть формул и получает или оценивает пользовательскую функцию в пользовательском пространстве имен или среде.

В качестве ссылки см. ключевое слово eval_env в http://patsy.readthedocs.org/en/latest/API-reference.html

from_formula это метод моделей, который реализует интерфейс формулы для patsy. Он использует eval_env, чтобы предоставить необходимую информацию Пэтси, которая по по умолчанию используется вызывающая среда пользователя. Это может быть перезаписано пользователем с соответствующим аргументом ключевого слова.

Самый простой способ определить eval_env - это целое число, указывающее уровень стека, который должен использовать Пэтси. from_formula увеличивает его, чтобы учесть дополнительный уровень в методах statsmodels.

Согласно комментариям, eval_env = 2 будет использовать следующий более высокий уровень от уровня, который создает модель, например, с model = smf.logit(..., eval_env=2).

Это создает модель, вызывает patsy и создает матрицу проектирования, model.fit() оценит ее и вернет экземпляр результатов.

Если вы хотите использовать eval для выполнения тяжелой работы своей функции, вы можете построить пространство имен из аргументов в wrapper и локальных переменных во внешнем фрейме:

wrapper_code = compile("smf.logit(formula, data).fit(**kwargs)",
                       "<WrapperFunction>","eval")
def wrapper(formula,data,**kwargs):
    outer_frame = sys._getframe(1)
    namespace = dict(outer_frame.f_locals)
    namespace.update(formula=formula, data=data, kwargs=kwargs, smf=smf)
    return eval(wrapper_code,namespace)

Я на самом деле не вижу в этом обмана, так как это похоже на то, что logit делает в любом случае, чтобы вызвать NameError, и до тех пор, пока wrapper_code не модифицируется и нет конфликтов имен (например, используя что-то под названием data), это должно делать то, что вы хотите.