代码之家  ›  专栏  ›  技术社区  ›  rescdsk

R:分离范围

  •  5
  • rescdsk  · 技术社区  · 14 年前

    我的问题是在用R编写模块时避免名称空间污染。

    现在,在我的R项目中,我有 functions1.R 具有 doFoo() doBar() , functions2.R 具有其他功能,以及 main.R 其中包含主程序,首先是 source('functions1.R'); source('functions2.R') ,然后调用其他函数。

    我从Mac OS X的r gui启动程序, source('main.R') . 这在第一次很好,但在那之后,通过程序第一次定义的变量第二次被定义。 functions*.R 是源代码,因此函数得到了一大堆额外的变量。

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

    那么其他人如何处理这种问题呢?有什么像 source() ,但这使得独立的名称空间不会落入主名称空间?制作一个包似乎是一个解决方案,但与Python相比,它似乎是一个巨大的麻烦,因为在Python中,源文件自动是一个单独的名称空间。

    有什么建议吗?谢谢您!

    4 回复  |  直到 14 年前
        1
  •  4
  •   hatmatrix    14 年前

    您要使用的主要功能是 sys.source() ,它将把您的函数/变量加载到一个名称空间(R中的“environment”)中,而不是全局名称空间。在R中可以做的另一件事是将名称空间附加到 search() 路径,这样就不需要直接引用命名空间。也就是说,如果“namespace1”在您的搜索路径上,则其中的一个函数,称为“fun1”,不需要作为 namespace1.fun1() 就像蟒蛇一样,但是 fun1() . [方法解析顺序:] 搜索() 将调用列表。要显式调用特定命名空间中的函数,许多可能的语法之一(尽管有点难看)是 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”,它们附加到 搜索() 路径(“fun2”将在 搜索() 在本例中列出)。这类似于做

    attach(NULL,name="fun1")
    sys.source("pathtofile/functions1.R",pos.to.env(2))
    

    为每个文件手动(“2”是 搜索() 路径)。就这样 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"))
    

    当然,如果您的项目规模越来越大,并且您有很多函数(和变量),那么编写一个包就是一种方法。

        2
  •  5
  •   Ian Fellows    14 年前

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

    (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))`
    
        3
  •  3
  •   Dirk is no longer here    14 年前

    如果您切换到使用包,您将得到作为一个附带的好处的名称空间(前提是您使用了名称空间文件)。使用包还有其他优点。

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

        4
  •  3
  •   doug    14 年前

    正如您所说,很好地避免名称空间污染,这只是一个努力划分名称空间并保持全局名称空间整洁的问题。

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

    了解/导航命名空间结构

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

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

    (我想,如果使用“attach”然后使用“detach”,它们也会实现相同的效果,但是“with”更快,而且您不必记住第二步。)换句话说,通过限制全局命名空间中可见的对象,可以部分避免命名空间冲突。