R:在环境中评估脚本

R: Evaluating a script in an environment

我想在指定环境中评估的脚本中加载库函数。

示例:

## foo.R
## -----

## blah blah 

library(extrafont)
loadfonts()

为方便起见,假设评估环境是基础环境:

sys.source("foo.R")
## Registering fonts with R
## Error in eval(expr, envir, enclos) : could not find function "loadfonts"

extrafont:::loadfonts() 替换 loadfonts() 效果更好,但仍然给出:

Error in get(as.character(FUN), mode = "function", envir = envir) : 
  object 'pdfFonts' of mode 'function' was not found

因为loadfonts()需要在grDevices中定义pdfFonts()

这既是一个不完全令人满意的答案,也是对@waterling 的长评论。

建议的解决方案是:

e<- new.env() 
source("foo.R", local=e)

source("foo.R", local=new.env())

实质上等同于:

sys.source("foo.R", envir=new.env())

它的工作原理与以下原因大致相同:

sys.source("foo.R", envir=as.environment("package:grDevices"))

如报错(见问题),未找到函数,pdfFonts() 是包的一部分 grDevices 上面的 sys.source 执行 [=24] 中的脚本=] 环境,因此找到了函数。相反,默认情况下 sys.source(..., envir=baseenv()) 并且基础环境出现在 grDevices 之前,因此找不到 pdfFonts()

第一个问题是我事先不知道我的脚本中会出现哪些函数。 在这种情况下,设置 envir=new.env() 是一种更通用的方法。默认情况下 new.env(parent=parent.frame()), 因此它具有与sys.source()相同的parent,即全局环境。因此,全局环境中可见的所有内容在带有 sys.source(..., envir=new.env()) 的脚本中都是可见的,即用户创建的每个 object 和用户加载的包。

这里的问题是我们不再隔离脚本,这使得它的可重现性和稳定性降低。事实上,这取决于我们调用 sys.source 的那一刻 R 内存中的内容。 为了使事情更实用,这意味着 foo.R 可能只是因为我们 通常 bar.R 之后调用它。

第二个问题是这不是实际的解决方案。 问题涉及 如何在环境 e 中 运行 脚本 foo.R 并且在需要时仍然访问不属于 e 的函数。 让一个(直接或通过它的 parents)访问这些功能的 e 实际上是一种变通方法,而不是解决方案。

如果这种解决方法是唯一可行的方法,恕我直言,最好的办法是让它只依赖于标准 R 包。
开始时,R 显示:

search()
## [1] ".GlobalEnv"        "package:stats"     "package:graphics" 
## [4] "package:grDevices" "package:utils"     "package:datasets" 
## [7] "package:methods"   "Autoloads"         "package:base"     

即八官packages/environments.
新 packages/environments,除非明确更改默认值,否则进入第二个槽位,第一个槽位之后的所有槽位移动一个位置。

myEnv=new.env()
attach(myEnv)
search()
##  [1] ".GlobalEnv"        "myEnv"             "package:stats"    
##  [4] "package:graphics"  "package:grDevices" "package:utils"    
##  [7] "package:datasets"  "package:methods"   "Autoloads"        
## [10] "package:base"     

所以我们可以在搜索路径中取后八位,也就是取这八位中的第一位继承其他的。我们需要:

pos.to.env(length(search()) - 7)
## <environment: package:stats>
## attr(,"name")
## [1] "package:stats"
## attr(,"path")
## [1] "path/to//R/R-x.x.x/library/stats"

因此:

sys.source("foo.R", envir=new.env(parent=pos.to.env(length(search()) - 7)))

或者可以采用标准的 R 参考包,比如 stats,及其 parents。

因此:

sys.source("foo.R", envir=new.env(parent=as.environment("package:stats")))

更新

我找到了

解决方案

至于剧本:

#foo.R
#-----
library(extrafont)
f=function() loadfonts()
environment(f) = as.environment("package:extrafont")
f()

要在新环境中执行:

sys.source("foo.R", envir=new.env(parent=baseenv()))

f() 现在可以访问包 extrafont 中的所有 object 以及之前加载的那些。

sys.source() 中创建一个 new.env() 和任何 parent 是使 environment() 分配工作所必需的。