Проблемы с пространством имен при вызове 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 ответа:
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
), это должно делать то, что вы хотите.