【问题标题】:Accessing variables in a function within a function在函数内访问函数中的变量
【发布时间】:2018-12-02 19:25:44
【问题描述】:

在 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 是全局常量、参数、对象的属性,还是只是任意变量...?如果您经常需要从某些函数中访问某些变量,那么它应该是一个对象不是很强烈的代码味道吗?

标签: r function parameter-passing


【解决方案1】:

我鼓励你阅读lexical scoping, 但我认为避免编写大量变量的好方法可能是:

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所迷惑; 见this discussion

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 

【讨论】:

    【解决方案2】:

    一种选择是从调用环境中显式获取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 中有很多函数,所以这会很麻烦,相当于将变量作为更可行的选项传递。词法作用域对我来说更有意义
    • 词法作用域不是一种选择,它是 R 的工作方式。无论如何,我强烈建议您重新考虑如何构建代码,因为所有这些方法都可能引入奇怪的行为,因为它们与 R 查找事物的位置混在一起。
    • 我实际上正在编写一个包,所有这些功能都是包的一部分。我需要在许多这些功能之间不断切换,从而导致问题。我想将所有环境设置为一个主环境,其中我的所有变量对 R 可见
    • 这听起来仍然是一种非常糟糕的方法,它会导致非常难以调试的范围界定错误。在编写会被大量使用的代码时,冗长不一定是坏事。
    • 添加到@alistaire 的评论中,一个编写良好的软件模块有low couplinghigh cohesion。根据您在 OP 和 cmets 中描述的内容,最好花一些时间重新设计您的函数,以便需要更多交互的事物更紧密地放在同一个函数中,以增加内聚力并减少耦合,或者将数据类型抽象为更大粒度的对象,可以在函数之间来回传递。
    【解决方案3】:

    编辑:

    @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 中有很多函数,所以这会很麻烦。词法作用域对我来说更有意义,尽管这似乎也是一个不错的选择。
    • 使用 quoteexpression 而不是 parse 会使其(稍微)不那么难看
    • @alistair。同意并删除无关的return()
    【解决方案4】:

    您可以将变量分配给全局环境并在函数内部使用。

    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
    

    【讨论】:

    • 使用全局环境不适合我使用
    猜你喜欢
    • 2018-06-08
    • 2015-04-29
    • 2017-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-23
    • 2018-06-28
    相关资源
    最近更新 更多