R: распутывание границ - PullRequest
       15

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

5 голосов
/ 14 апреля 2010

Мой вопрос касается предотвращения загрязнения пространства имен при написании модулей в R.

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

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

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

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

Какие-нибудь советы? Спасибо!

Ответы [ 4 ]

5 голосов
/ 14 апреля 2010

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

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

б) Убирать вещи вручную :

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

#stuff happens

rm(list = setdiff(ls(),prior_variables))`
4 голосов
/ 14 апреля 2010

Основной функцией, которую вы хотите использовать, является 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))

или

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

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

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

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

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

3 голосов
/ 14 апреля 2010

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

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

...