Проблемы с пространством имен при вызове 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. Есть ли безопасный, Pythonic способ справиться с этой ситуацией, не зная априори, каковы имена функций или сколько функций потребуется?

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


person chriswhite    schedule 22.04.2016    source источник
comment
Я не знаю подробностей (слишком много волшебства, на мой вкус), но строка документации statsmodels.base.model.Model.from_formula описывает eval_env kwd в **kwarg, который можно увеличить на 1. from_formula наследуется всеми или большинством моделей.   -  person Josef    schedule 22.04.2016
comment
Да, я пробовал это; похоже, не работает, но, возможно, я не назвал это правильно.   -  person chriswhite    schedule 22.04.2016
comment
Вы пробовали поставить 3? В аналогичном случае я использовал оболочку try..except, чтобы выяснить, в какой глубине находятся пользовательские функции.   -  person Josef    schedule 22.04.2016
comment
пример: statsmodels.basedata.ModelData.__setstate__, который пытается воссоздать формулу и дизайн во время распаковки. Я написал это методом проб и ошибок, основываясь на нескольких примерах.   -  person Josef    schedule 22.04.2016
comment
@ user333700 опубликуйте это как ответ, и я приму его; два примечания: 1.) мне пришлось установить eval_env = 2 и 2.) это ключевое слово для logit(..), а не для fit(...). (Не то, чтобы вы имели в виду, что это было, но я этого не осознавал).   -  person chriswhite    schedule 22.04.2016
comment
smf.logit — это псевдоним sm.Logit.from_formula, поэтому я имел в виду from_formula.   -  person Josef    schedule 23.04.2016
comment
Да, определенно; Я просто хотел уточнить, что ключевое слово eval_env было не для метода fit, а для начального вызова формулы (будь то через API или нет).   -  person chriswhite    schedule 23.04.2016


Ответы (2)


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

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

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

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

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

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

person Josef    schedule 22.04.2016
comment
если есть несколько вложенных оболочек, имеет ли смысл принять это в качестве аргумента и передать после его увеличения. нравится def f(..., eval_env=1): ... smf.logit(..., eval_env=eval_env+1) - person Tadhg McDonald-Jensen; 23.04.2016
comment
Если я правильно понял ваш комментарий, то это то, что делает from_formula, github.com/statsmodels/statsmodels/blob/master/statsmodels/base/ например. увеличьте, если это предусмотрено, и передайте. В целом, я бы проверил проблемы безопасности, прежде чем расширять подходы eval. - person Josef; 23.04.2016

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

person Tadhg McDonald-Jensen    schedule 22.04.2016
comment
О, это очень интересно, и я могу подумать о том, чтобы двигаться дальше, если это станет более сложным. Спасибо! - person chriswhite; 23.04.2016
comment
Одна проблема, которую я не знаю, как это будет работать в подобных случаях, заключается в том, что нам (модели или результатам) снова нужна одна и та же информация, например. для оценки или преобразования независимых переменных в predict. Пэтси хранит информацию и использует некоторую eval магию, насколько я знаю. - person Josef; 23.04.2016