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