Мой вопрос о предотвращении загрязнения пространства имен при записи модулей в R.
Прямо сейчас, в моем проекте R, я имею functions1.R
с doFoo()
и doBar()
, functions2.R
с другими функциями, и main.R
с основной программой в нем, которая сначала делает source('functions1.R'); source('functions2.R')
, и затем вызывает другие функции.
Я запускал программу от GUI R в Mac OS X, с source('main.R')
. Это прекрасно в первый раз, но после этого, переменные, которые были определены в первый раз через программу, определяются во второй раз functions*.R
получены, и таким образом, функции получают целый набор дополнительных определенных переменных.
Я не хочу это! Я хочу ошибку "неопределенной переменной", когда моя функция использует переменную, она не была должна! Дважды это дало мне очень поздно ночи отладки!
Таким образом, как другие люди имеют дело с этим видом проблемы? Есть ли что-то как source()
, но это делает независимое пространство имен, которое не падает до основного? Создание пакета походит на одно решение, но на большую боль в торце по сравнению с, например, Python, походит, где исходный файл является автоматически отдельным пространством имен.
Какие-либо подсказки?Спасибо!
Основная функция, которую вы хотите использовать, - это sys.source ()
, которая загрузит ваши функции / переменные в пространство имен (" environment "в R) кроме глобального. Еще одна замечательная вещь, которую вы можете сделать в R, - это присоединить пространства имен к вашему пути search ()
, чтобы вам не нужно было напрямую ссылаться на пространство имен. То есть, если «namespace1» находится на вашем пути поиска, функцию в нем, скажем «fun1», не нужно вызывать как namespace1.fun1 ()
, как в Python, но как fun1 ( )
.[Порядок разрешения методов:] Если имеется много функций с одинаковыми именами, будет вызвана функция в среде, которая появляется первой в списке search ()
. Чтобы вызвать функцию в определенном пространстве имен явно, один из многих возможных синтаксисов - хотя и немного уродливый - это get ("fun1", "namespace1") (...)
где ...
- аргументы fun1 ()
. Это также должно работать с переменными, используя синтаксис get ("var1", "namespace1")
. Я делаю это все время (обычно я загружаю только функции, но различие между функциями и переменными в R невелико), поэтому я написал несколько вспомогательных функций, которые загружаются из моего ~ / .Rprofile
.
name.to.env <- function(env.name)
## returns named environment on search() path
pos.to.env(grep(env.name,search()))
attach.env <- function(env.name)
## creates and attaches environment to search path if it doesn't already exist
if( all(regexpr(env.name,search())<0) ) attach(NULL,name=env.name,pos=2)
populate.env <- function(env.name,path,...) {
## populates environment with functions in file or directory
## creates and attaches named environment to search() path
## if it doesn't already exist
attach.env(env.name)
if( file.info(path[1])$isdir )
lapply(list.files(path,full.names=TRUE,...),
sys.source,name.to.env(env.name)) else
lapply(path,sys.source,name.to.env(env.name))
invisible()
}
Пример использования:
populate.env("fun1","pathtofile/functions1.R")
populate.env("fun2","pathtofile/functions2.R")
и так далее, что создаст два отдельных пространства имен: «fun1» и «fun2», которые будут прикреплены к пути search ()
(«fun2» будет выше в списке search ()
в данном случае). Это похоже на выполнение чего-то вроде
attach(NULL,name="fun1")
sys.source("pathtofile/functions1.R",pos.to.env(2))
вручную для каждого файла ("2" - позиция по умолчанию в пути search ()
). Способ записи populate.env ()
: если каталог, скажем «functions /», содержит много файлов R без конфликтующих имен функций, вы можете вызвать его как
populate.env("myfunctions","functions/")
, чтобы загрузить все функции (и переменные) в единое пространство имен. С помощью name.to.env ()
вы также можете сделать что-то вроде
with(name.to.env("fun1"), doStuff(var1))
или
evalq(doStuff(var1), name.to.env("fun1"))
. Конечно, если ваш проект разрастается и у вас много-много функций (и переменных), напишите пакет - это то, что нужно.
Если вы переключитесь на использование пакетов, вы получите пространства имен в качестве побочного преимущества (при условии, что вы используете файл NAMESPACE). Есть и другие преимущества использования пакетов.
Если вы действительно пытаетесь избегать пакетов (чего не следует), вы можете попробовать назначить свои переменные в определенных средах.
Я бы рассмотрел два возможных решения этой проблемы.
a) Мыслить более функционально. Не создавайте никаких переменных вне функции. так, например, main.R должен содержать одну функцию main(), которая является источником других файлов и выполняет работу. когда main возвращается, ничего из беспорядка не остается.
b) Наведите порядок вручную:
#main.R
prior_variables <- ls()
source('functions1.R')
source('functions2.R')
#stuff happens
rm(list = setdiff(ls(),prior_variables))`
Чтобы избежать загрязнения пространства имен, как вы выразились, нужно всего лишь тщательно разбить пространство имен и сохранить незагроможденное глобальное пространство имен.
Вот основные функции для этих двух типов задач:
При запуске R создает новую среду для хранения всех объектов, созданных во время этого сеанса - это «глобальная среда».
# to get the name of that environment:
globalenv()
Но это не корневая среда. Корень - это среда, называемая «пустая среда» - все среды связаны с ней:
emptyenv()
returns: <environment: R_EmptyEnv>
# to view all of the chained parent environments (which includes '.GlobalEnv'):
search()
workspace1 = new.env()
is.environment(workspace1)
returns: [1] TRUE
class(workspace1)
returns: [1] "environment"
# add an object to this new environment:
with(workspace1, attach(what="/Users/doug/Documents/test_obj.RData",
name=deparse(substitute(what)), warn.conflicts=T, pos=2))
# verify that it's there:
exists("test_obj", where=workspace1)
returns: [1] TRUE
# to locate the new environment (if it's not visible from your current environment)
parent.env(workspace1)
returns: <environment: R_GlobalEnv>
objects(".GlobalEnv")
returns: [1] "test_obj"
Исходя из python и др., Эта система (сначала) казалась мне комнатой полный карнавальных зеркал. С другой стороны, R Gurus, похоже, вполне с этим справляется. Я уверен, что этому есть ряд причин, но моя интуиция подсказывает, что они не позволяют окружающей среде сохраняться. Я заметил, что новички в R используют "прикрепить", как в "attach (" this_dataframe "); Я заметил, что опытные пользователи R этого не делают; вместо этого они используют «с», например
with(this_dataframe, tapply(etc....))
(я полагаю, они достигли бы того же результата, если бы использовали «прикрепить», а затем «отсоединить», но «с» работает быстрее, и вам не нужно запоминать второй шаг.) Другими словами, конфликтов пространства имен можно частично избежать за счет ограничения объектов, видимых из глобального пространства имен.