如何避免使用eval和parse?

15

我编写了一个函数,用于获取包含其他函数脚本的文件,并将这些函数存储在另一个环境中,以避免混杂在全局环境中。代码可以运行,但是包含三个 eval(parse(...)) 的实例:

# sourceFunctionHidden ---------------------------
# source a function and hide the function from the global environment
sourceFunctionHidden <- function(functions, environment = "env", ...) {
    if (environment %in% search()) {
        while (environment %in% search()) {
            if (!exists("counter", inherits = F)) counter <- 0
            eval(parse(text = paste0("detach(", environment, ")")))
            counter <- counter + 1 
        }
        cat("detached", counter, environment, "s\n")
    } else {cat("no", environment, "attached\n")}
    if (!environment %in% ls(.GlobalEnv, all.names = T)) {
        assign(environment, new.env(), pos = .GlobalEnv)
        cat("created", environment, "\n")
    } else {cat(environment, "already exists\n")}
    sapply(functions, function(func) {
        source(paste0("C:/Users/JT/R/Functions/", func, ".R"))
        eval(parse(text = paste0(environment, "$", func," <- ", func)))
        cat(func, "created in", environment, "\n")
    })
    eval(parse(text = paste0("attach(", environment, ")")))
    cat("attached", environment, "\n\n")
}

很多关于eval(parse(...))结构的亚优化问题已经被写出(请参见这里这里)。然而,我发现的大部分讨论都是关于替代子集策略的。我的代码中第一个和第三个eval(parse(...))实例不涉及子集(第二个实例可能与子集有关)。
是否有一种方法可以调用new.env(...)[environment name]$[function name] <- [function name]attach(...)而不必使用eval(parse(...))?谢谢。
注:我不想改变我的函数名称为.name以在全局环境中隐藏它们。

1
刚刚发现 eval(parse(text = paste0("detach(", environment, ")"))) 可以被替换为 detach(environment, character.only = T)。关于改进 eval(parse(text = paste0("attach(", environment, ")"))) 的问题仍然存在。 - Josh
3个回答

5
值得一提的是,函数source实际上使用了eval(parse(...)),尽管方式有些微妙。首先,使用.Internal(parse(...))创建表达式,经过更多处理后再传递给eval。因此,在这种情况下,eval(parse(...))似乎已经足够好了,得到了R核心团队的认可。
也就是说,您不需要费力地将函数源码引入新环境中。source提供了一个local参数,可以用于精确控制。
一个例子:
env = new.env()
source('test.r', local = env)

测试它是否可行:

env$test('hello', 'world')
# [1] "hello world"
ls(pattern = 'test')
# character(0)

以下是一个使用此功能的示例test.r文件:

test = function(a,b) paste(a,b)

谢谢,我错过了source()的那个方面。但是,如果我将代码行更改为source(paste0("C:/Users/JT/R/Functions/", func, ".R"), local = environment),我会收到错误消息Error in source(paste0("C:/Users/JT/R/Functions/", func, ".R"), local = environment) : 'local' must be TRUE, FALSE or an environment。有没有办法将来自environment"env"转换为env - Josh
你应该创建一个环境来保存。例如,如我所示的 env = new.env()。然后将该环境作为参数传递。如果您需要使用字符字符串(例如您的示例中的 environemt,尽管使用保留字作为名称是不好的做法)来命名新环境,则可以使用 assign(environment, new.env()) - dww

3
如果您想将其从global_env中移除,请将其放入一个包中。在R社区中,人们通常会将一些常用的辅助函数放入自己的个人包中。

1
这并不像你想的那么难!我认为你要编写的函数更难,而且更加复杂。有许多关于编写程序包的教程。 - thc
我还没有时间制作一个软件包,但是如果这篇文章描述的如此简单,那真是太棒了!我要把所有东西都打包成一个软件包! - Josh

0

简短回答:将引用的字符串转换为对象名称的正确方法是使用assign()get()。请参见this post

详细回答:@dww提供的关于直接将source()导入特定环境的答案,使我将第二个eval(parse(...))实例更改如下:

# old version
source(paste0("C:/Users/JT/R/Functions/", func, ".R"))
eval(parse(text = paste0(environment, "$", func," <- ", func)))
# new version
source(
    paste0("C:/Users/JT/R/Functions/", func, ".R"), 
    local = get(environment)
)

@dww的回答也让我探索了attach()attach()有一个参数,允许指定要将输出发送到的环境。这使我更改了下面的第三个eval(parse(...))实例。请注意使用get()将来自environment"env"转换为attach()需要的未引用的env

# old version
eval(parse(text = paste0("attach(", environment, ")")))
# new version
attach(get(environment), name = environment)

最后,在这个过程中的某个时刻,我想起了 `rm()` 函数有一个 `character.only` 参数。`detach()` 函数也接受相同的参数,因此我将第二个 `eval(parse())` 实例更改为以下内容:
# old version
eval(parse(text = paste0("detach(", environment, ")")))
# new version
detach(environment, character.only = T)

所以我的新代码是:

# sourceFunctionHidden ---------------------------
# source a function and hide the function from the global environment
sourceFunctionHidden <- function(functions, environment = "env", ...) {
    if (environment %in% search()) {
        while (environment %in% search()) {
            if (!exists("counter", inherits = F)) counter <- 0
            detach(environment, character.only = T)
            counter <- counter + 1 
        }
        cat("detached", counter, environment, "s\n")
    } else {cat("no", environment, "attached\n")}
    if (!environment %in% ls(.GlobalEnv, all.names = T)) {
        assign(environment, new.env(), pos = .GlobalEnv)
        cat("created", environment, "\n")
    } else {cat(environment, "already exists\n")}
    sapply(functions, function(func) {
        source(
            paste0("C:/Users/JT/R/Functions/", func, ".R"), 
            local = get(environment)
        )
        cat(func, "created in", environment, "\n")
    })
    attach(get(environment), name = environment)
    cat("attached", environment, "\n\n")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接