R: распутывание прицелов

Мой вопрос заключается в том, чтобы избежать загрязнения пространства имен при написании модулей в R.

Прямо сейчас в моем проекте R у меня есть functions1.R с doFoo() и doBar(), functions2.R с другими функциями и main.R с основной программой, которая сначала выполняет source('functions1.R'); source('functions2.R'), а затем вызывает другие функции.

Я запускал программу из графического интерфейса R в Mac OS X с source('main.R'). Это хорошо в первый раз, но после этого переменные, которые были определены в программе в первый раз, определяются во второй раз functions*.R из источника, и поэтому функции получают целую кучу дополнительных переменных.

Я не хочу этого! Мне нужна ошибка «неопределенная переменная», когда моя функция использует переменную, которой не следует! Дважды это давало мне очень поздние ночи отладки!

Так как же другие люди справляются с такого рода проблемами? Есть ли что-то вроде source(), но это делает независимое пространство имен, которое не попадает в основное? Создание пакета кажется одним из решений, но это кажется большой головной болью по сравнению, например, с. Python, где исходный файл автоматически является отдельным пространством имен.

Какие-нибудь советы? Благодарю вас!


person rescdsk    schedule 14.04.2010    source источник
comment
если бы я мог добавить одну функцию в стандартный дистрибутив, это была бы она. Я бы очень хотел сделать что-то вроде этого: импортировать временные ряды как TS   -  person doug    schedule 14.04.2010
comment
Создать пакет с помощью package.skeleton несложно, и в долгосрочной перспективе это принесет много преимуществ.   -  person Shane    schedule 14.04.2010


Ответы (4)


Основная функция, которую вы хотите использовать, — это sys.source(), которая будет загружать ваши функции/переменные в пространство имен («окружение» в 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))

or

evalq(doStuff(var1), name.to.env("fun1"))

Конечно, если ваш проект разрастается и у вас много-много функций (и переменных), написание пакета — это то, что вам нужно.

person hatmatrix    schedule 14.04.2010
comment
В моей функции у меня есть логический аргумент reload, который, если он установлен на TRUE, вызывает detach, если уже есть точное имя. Это помогает, когда ваши коды часто изменяются во время анализа. - person Marek; 14.04.2010
comment
в attach(NULL, ...) должно быть attach(NULL, name="fun1") И спасибо, это действительно здорово! - person rescdsk; 14.04.2010
comment
дох! @rescdsk - спасибо, что указали на это. Я сделал изменение выше для других, которые хотят сделать что-то подобное. и @Marek - я также думал о том, что делать, когда среда уже подключена ... моя просто загружает изменения в существующую среду, но я согласен, что ваш способ может быть чище, поскольку он не сохраняет удаленные функции или переменные, если они удаляются из исходного кода. - person hatmatrix; 15.04.2010
comment
@rescdsk — вы также можете создать новую среду вне списка search() с помощью чего-то вроде myenv ‹- new.env() и использовать newenv в качестве аргумента для 'envir' (вместо использования pos.to.env() или name.to.env()) для функций get(), assign(), with(), inside() или evalq() для установки и получения значений переменных/функций в вашей среде (средах) или из нее. - person hatmatrix; 15.04.2010

Я бы рассмотрел два возможных решения этой проблемы.

а) Думайте более функционально. Не создавайте никаких переменных вне функции. так, например, main.R должен содержать одну функцию main(), которая исходит из других файлов и выполняет работу. когда основной возвращается, ничего из беспорядка не останется.

б) Очистить все вручную:

#main.R
prior_variables <- ls()
source('functions1.R')
source('functions2.R')

#stuff happens

rm(list = setdiff(ls(),prior_variables))`
person Ian Fellows    schedule 14.04.2010
comment
Я согласен с тем, что большинство переменных следует хранить в локальной среде (например, внутри функций). - person hatmatrix; 15.04.2010

Если вы переключаетесь на использование пакетов, вы получаете пространства имен в качестве побочного преимущества (при условии, что вы используете файл NAMESPACE). Есть и другие преимущества использования пакетов.

Если вы действительно пытаетесь избежать пакетов (чего вам не следует делать), вы можете попробовать назначить свои переменные в определенных средах.

person Dirk Eddelbuettel    schedule 14.04.2010
comment
Я действительно ничего не имею против пакетов, за исключением того, что для их создания требуется много шагов. Насколько я могу судить, вы должны сделать package.skeleton(), затем собрать R CMD, затем установить R CMD --- должен ли я делать это каждый раз, когда я вношу изменения в свой код? Это кажется неуклюжим способом разработки. - person rescdsk; 14.04.2010
comment
У упаковки есть множество преимуществ, и это часто обсуждалось здесь, чтобы мы не повторяли. Если вам это не нравится, вы не используете его. Ваша потеря, а не моя :) Тем не менее, для небольших постепенных изменений у вас есть другие варианты (fix(), edit(), source(), ...), но для всего, что имеет некоторую структуру, несколько частое использование, размер больше, чем мизерный , ... Мне нравятся пакеты. Много. - person Dirk Eddelbuettel; 14.04.2010

Что ж, предотвращение загрязнения пространства имен, как вы выразились, — это всего лишь вопрос старательного разделения пространства имен и поддержания вашего глобального пространства имен в чистоте.

Вот основные функции для этих двух видов задач:

Понимание/навигация по структуре пространства имен

При запуске 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, кажется, вполне довольны этим. Я уверен, что тому есть ряд причин, но моя интуиция подсказывает, что они не позволяют окружающей среде сохраняться. Я заметил, что новички в R используют 'attach', например, attach('this_dataframe'); Я заметил, что опытные пользователи R этого не делают; вместо этого они используют «с», например,

with(this_dataframe, tapply(etc....))

(Я полагаю, они достигли бы того же результата, если бы использовали «присоединить», а затем «отсоединить», но «с» быстрее, и вам не нужно запоминать второй шаг.) Другими словами, коллизии пространств имен частично избегаются за счет ограничения объекты, видимые из глобального пространства имен.

person doug    schedule 14.04.2010