【问题标题】:R: disentangling scopesR:解开范围
【发布时间】:2010-04-14 02:28:35
【问题描述】:

我的问题是关于在 R 中编写模块时避免命名空间污染。

现在,在我的 R 项目中,我有 functions1.RdoFoo()doBar()functions2.R 有其他功能,main.R 有主程序,它首先执行source('functions1.R'); source('functions2.R'),然后调用其他函数。

我一直在 Mac OS X 的 R GUI 中使用source('main.R') 启动程序。第一次没问题,但在那之后,通过程序第一次定义的变量被第二次定义functions*.R,因此函数定义了一大堆额外的变量。

我不想这样!当我的函数使用不应该使用的变量时,我想要一个“未定义的变量”错误!两次,这让我调试得很晚!

那么其他人是如何处理这类问题的呢?有没有像source() 这样的东西,但这会产生一个独立的命名空间,不会落入主命名空间?制作一个包裹似乎是一种解决方案,但与例如Python,其中源文件自动成为单独的命名空间。

有什么建议吗?谢谢!

【问题讨论】:

  • 如果我可以在标准分布中添加一个功能,那就是它。我真的很想做这样的事情:“将时间序列导入为 TS”
  • package.skeleton 创建一个包并不难,从长远来看会带来很多好处。

标签: r scope


【解决方案1】:

我将探索两种可能的解决方案。

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))`

【讨论】:

  • 我同意大多数变量应该保存在本地环境中(例如,在函数中)。
【解决方案2】:

您要使用的主要函数是sys.source(),它将在全局命名空间(R 中的“环境”)中加载您的函数/变量。您可以在 R 中做的另一件很棒的事情是将名称空间附加到您的 search() 路径,这样您就不需要直接引用名称空间。也就是说,如果“namespace1”在您的搜索路径上,则其中的一个函数,比如“fun1”,不需要像 Python 中那样被称为 namespace1.fun1(),而是 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"))

当然,如果您的项目变得很大,并且您有很多很多的函数(和变量),那么编写一个包就是要走的路。

【讨论】:

  • 在我的函数中,我有逻辑 reload 参数,如果设置为 TRUE 会导致 detach 如果已经附加了确切的名称。当您的代码在分析过程中经常被修改时,它会有所帮助。
  • attach(NULL, ...),应该是attach(NULL, name="fun1") 谢谢,这真的很棒!
  • 哇! @rescdsk - 感谢您指出这一点。我为其他希望做类似事情的人进行了上述更改。和@Marek - 我还考虑了当环境已经附加时该怎么办......我的只是将修改加载到现有环境中,但我同意你的方式可能更干净,因为它不会保留已删除的函数或变量,如果它们从源代码中删除。
  • @rescdsk - 您还可以使用 myenv
【解决方案3】:

如果您切换到使用包,您可以获得命名空间作为附带好处(前提是您使用 NAMESPACE 文件)。使用包还有其他优点。

如果您真的想避免使用包(您不应该这样做),那么您可以尝试在特定环境中分配变量。

【讨论】:

  • 我对包没有任何反对意见,只是制作它们需要很多步骤。据我所知,您必须执行 package.skeleton(),然后 R CMD 构建,然后 R CMD 安装 --- 每次更改代码时都必须这样做吗?这似乎是一种笨拙的开发方式。
  • 包装有很多好处,这里经常讨论,我们不要重复。如果你不喜欢它,你就不要使用它。您的损失,不是我的 :) 也就是说,对于小的增量更改,您还有其他选择(fix()、edit()、source()、...),但对于任何具有某种结构、使用频率较高、大于微小尺寸的东西, ...我倾向于喜欢包裹。很多。
【解决方案4】:

正如您所说,避免命名空间污染只是努力划分命名空间并保持全局命名空间整洁。

以下是这两种任务的基本功能:

理解/导航命名空间结构

在启动时,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'代替,例如,

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

(我想如果他们使用 'attach' 然后 'detach' 会达到同样的效果,但 'with' 更快,您不必记住第二步。)换句话说,命名空间冲突在部分是通过限制从全局命名空间中可见的对象。

【讨论】:

    猜你喜欢
    • 2014-02-27
    • 2014-10-20
    • 2019-08-08
    • 2016-04-23
    • 2017-03-20
    • 1970-01-01
    • 1970-01-01
    • 2010-09-09
    • 1970-01-01
    相关资源
    最近更新 更多