在一个函数内部访问另一个函数中的变量

12

在R中运行一个函数时,我在其中运行另一个函数。我的代码大致如下:

f_a <- function(b, c){
    return(b + c)
}

f_e <- function(){
    b = 2
    c = 2 
    d = f_a(b, c)
    print(d)
}

这个可以正常工作。我想做的是不将变量b, c传递给函数f_a,我想实现这样的操作(它会产生错误)。

f_a <- function(){
    return(b + c)
}

f_e <- function(){
    b = 2
    c = 2
    d = f_a()
    print(d)
}

有没有使用环境、搜索路径或其他任何方式来完成这个任务的方法?


b、c是全局常量、参数、对象属性还是任意变量?如果你经常需要从某些函数内访问某些变量,那不就是代码异味强烈表明它应该是一个对象吗? - smci
4个回答

15

我确实鼓励您阅读有关词法作用域的内容,但我认为避免编写许多变量的好方法可能是:

get_args_for <- function(fun, env = parent.frame(), inherits = FALSE, ..., dots) {
    potential <- names(formals(fun))

    if ("..." %in% potential) {
        if (missing(dots)) {
            # return everything from parent frame
            return(as.list(env))
        }
        else if (!is.list(dots)) {
            stop("If provided, 'dots' should be a list.")
        }

        potential <- setdiff(potential, "...")
    }

    # get all formal arguments that can be found in parent frame
    args <- mget(potential, env, ..., ifnotfound = list(NULL), inherits = inherits)
    # remove not found
    args <- args[sapply(args, Negate(is.null))]
    # return found args and dots
    c(args, dots)
}

f_a <- function(b, c = 0, ..., d = 1) {
    b <- b + 1
    c(b = b, c = c, d = d, ...)
}

f_e <- function() {
    b <- 2
    c <- 2
    arg_list <- get_args_for(f_a, dots = list(5))
    do.call(f_a, arg_list)
}

> f_e()
b c d   
3 2 1 5 

默认情况下设置 inherits = FALSE 确保我们只从指定的环境中获取变量。 在调用 get_args_for 时,我们也可以将 dots = NULL 设置为不传递所有变量, 但将省略号留空。

然而,这仍然不完全健壮, 因为 dots 只是简单地附加在结尾处, 如果某些参数没有命名,则可能被按位置匹配。 此外,如果一些值在调用中应该是 NULL,那么很难检测到它们。


我强烈建议不要在 R 包内使用以下内容。 不仅代码看起来很丑, 还会收到 R 的 CMD 检查关于未定义全局变量的提示。

其他选项。

f_a <- function() {
    return(b + c)
}

f_e <- function() {
    b <- 2
    c <- 2
    # replace f_a's enclosing environment with the current evaluation's environment
    environment(f_a) <- environment()
    d <- f_a()
    d
}

> f_e()
[1] 4

类似上述内容的代码可能无法在R程序包中运行,因为我认为程序包中的函数会锁定其封闭环境。

或者:

f_a <- function() {
    with(parent.frame(), {
        b + c
    })
}

f_e <- function() {
    b <- 2
    c <- 2
    f_a()
}

> f_e()
[1] 4

这样,您就不会永久修改其他函数的封闭环境。但是,两个函数将共享一个环境,因此可能会发生这样的情况:

f_a <- function() {
    with(parent.frame(), {
        b <- b + 1
        b + c
    })
}

f_e <- function() {
    b <- 2
    c <- 2
    d <- f_a()
    c(b,d)
}

> f_e()
[1] 3 5

调用内部函数会修改外部环境中的值。

另一种稍微灵活一些的选择是使用 eval 临时仅修改封闭环境。 但是,有些 R 函数通过“黑魔法”检测它们当前的执行环境, 无法被 eval 欺骗; 请参见这个讨论

f_a <- function() {
    b <- b + 1
    b + c
}

f_e <- function() {
    b <- 2
    c <- 2
    # use current environment as enclosing environment for f_a's evaluation
    d <- eval(body(f_a), list(), enclos=environment())
    c(b=b, d=d)
}

> f_e()
b d 
2 5 

4

一种方法是明确从调用环境中获取ab

f_a <- function(){
    get('b', envir = parent.frame()) + get('c', envir = parent.frame())
}

f_e <- function(){
    b = 2
    c = 2
    d = f_a()
    d
}

f_e()
#> [1] 4

或者,您可以使用quote来延迟评估,然后使用eval在调用环境中评估代码,从而有效地完成相同的操作:

f_a <- function(){
    eval(quote(b + c), parent.frame())
}

然而,这并不是一种编写代码的健壮方式,因为它限制了成功调用f_a的可能方式。显式传递变量会使代码更易于跟踪。


我在我的f_a中有很多函数,这将非常繁琐,并且传递变量作为更可行的选项。词法作用域对我来说更有意义。 - Siddd
词法作用域不是一个选择,而是R的工作方式。尽管如此,我强烈建议您重新考虑如何构建代码,因为所有这些方法都可能会引入奇怪的行为,因为它们会干扰R查找事物的位置。 - alistaire
我正在撰写一个软件包,所有这些函数都是该软件包的一部分。我需要不断在许多这些函数之间切换,因此出现了问题。我想将所有环境设置为一个主环境,在这个主环境中,所有我的变量对于 R 都是可见的。 - Siddd
4
这种做法听起来仍然很糟糕,会导致非常难以调试的作用域错误。在编写经常使用的代码时,冗长并不一定是坏事。 - alistaire
2
除了@alistaire的评论之外,一个良好编写的软件模块具有低耦合和高内聚。根据您在原始帖子和评论中所描述的内容,花费一些时间重新设计函数的设计将是很好的选择,以便需要更多交互的事物更加接近,位于同一个函数中以增加内聚性并减少耦合,或将数据类型抽象成一个更大的对象,可以在函数之间来回传递。 - Len Greski
哦,天啊... Siddd,请阅读我添加到我的答案中的评论。 - Alexis

2

编辑:

@alistaire建议使用quote来构建表达式,这提出了更进一步的选择,看起来甚至更不丑陋:

expr_env <- new.env()
   expr_env$f_a <- quote(b+c)
   expr_env$f_z <- quote(x+y)

f_e<-function(){
    b=2
    c=2
    d=eval( expr_env$f_a)
    print(d)
}

定义函数时使用local是可接受的替代方案吗?
 f_e<-function(){
     b=2
     c=2
     d<-local({
          b+c
              })

     print(d)
 }
 f_e()
[1] 4

另一种选择是只返回解析树,然后在函数的“本地”环境中完成评估。但我觉得这种方法有点“丑陋”:
expr_list<-function(){  f_a <- quote(b+c)
                        f_z <- quote(x+y)
list(f_a=f_a,f_z=f_z) }

f_e<-function(){
    b=2
    c=2
    d=eval( (expr_list()$f_a))
    print(d)
}

我在我的f_a中有很多函数,所以这会非常麻烦。对我来说,词法作用域更有意义,尽管这似乎也是一个不错的选择。 - Siddd
使用 quoteexpression 而不是 parse 将使其(稍微)不那么丑陋。 - alistaire
@alistair。同意并删除了多余的return() - IRTFM

0

你可以将变量分配到全局环境中,在函数内部使用。

f_a <- function(){
    return(b + c)
}

f_e <- function(){
    assign("b", 2, envir = .GlobalEnv)
    assign("c", 2, envir = .GlobalEnv)
    d = f_a()
    print(d)
}

# > f_e()
# [1] 4

使用全局环境不是我的选择。 - Siddd

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