在R中,如何让函数内部的变量对该函数内部的较低级别函数可用?(使用with、attach、environment)

31

更新2 @G. Grothendieck发布了两种方法。第二种方法是在函数内部更改函数环境。这解决了我的过多编码复制的问题。我不确定在将我的脚本制作成包时,这是否是通过CRAN检查的好方法。当我有一些结论时,我会再次更新。

更新

我正在尝试将许多输入参数变量传递给f2,并且不想在函数内部对每个变量进行索引,例如env$c, env$d, env$calls,这就是为什么我尝试在f5f6(一个修改后的f2)中使用with的原因。然而,assign{}内部与with不起作用,将assign移到外部可以完成工作,但在实际情况下,我有几个assignwith表达式内部,我不知道如何轻松地将它们移出with函数。

以下是一个示例:

## In the <environment: R_GlobalEnv>
a <- 1
b <- 2
f1 <- function(){
    c <- 3
d <- 4
f2 <- function(P){
    assign("calls", calls+1, inherits=TRUE)
    print(calls)
    return(P+c+d)
 }
calls <- 0
v <- vector()
for(i in 1:10){
    v[i] <- f2(P=0)
    c <- c+1
    d <- d+1
  }
 return(v)
}
f1()

函数 f2 嵌套在 f1 内部,当调用 f2 时,它会在环境 environment(f1) 中查找变量 calls,c,d。这正是我想要的。

然而,当我想在其他函数中使用 f2 时,我将定义此函数在全局环境中,称其为 f4

f4 <- function(P){
  assign("calls", calls+1, inherits=TRUE)
  print(calls)
  return(P+c+d)
}

这不可行,因为它将在全局环境中查找calls,c,d,而不是在调用函数的函数内部查找。例如:

f3 <- function(){
  c <- 3
  d <- 4
  calls <- 0
  v <- vector()
  for(i in 1:10){
    v[i] <- f4(P=0) ## or replace here with f5(P=0)
    c <- c+1
    d <- d+1
  }
  return(v)
}
f3()

安全的做法应该是在f4的输入参数中定义calls,c,d并将这些参数传递给f4。然而,在我的情况下,有太多变量要传递给这个函数f4,最好能够将它作为一个环境传递,并告诉f4在调用f3时只查找environment内部,而不要查找全局环境(environment(f4))。

我现在解决的方法是将环境用作列表,并使用with函数。

f5 <- function(P,liste){
  with(liste,{
     assign("calls", calls+1, inherits=TRUE)
     print(calls)
     return(P+c+d)
     }
  )
}
f3 <- function(){
  c <- 3
  d <- 4
  calls <- 0
  v <- vector()
  for(i in 1:10){
    v[i] <- f5(P=0,as.list(environment())) ## or replace here with f5(P=0)
    c <- c+1
    d <- d+1
  }
  return(v)
}
f3()

然而,现在assign("calls", calls+1, inherits=TRUE)不能像原来那样正常工作,因为assign没有修改原始对象。变量calls与最优化函数相关,在这个函数中,目标函数是f5。这就是我使用assign而不是将calls作为输入参数传递的原因。对我来说,使用attach也不清楚。以下是我纠正assign问题的方法:

f7 <- function(P,calls,liste){
  ##calls <<- calls+1
  ##browser()
  assign("calls", calls+1, inherits=TRUE,envir = sys.frame(-1))
  print(calls)
  with(liste,{
    print(paste('with the listed envrionment, calls=',calls))
    return(P+c+d)
  }
  )
}
########
##################
f8 <- function(){
  c <- 3
  d <- 4
  calls <- 0
  v <- vector()
  for(i in 1:10){
    ##browser()
    ##v[i] <- f4(P=0) ## or replace here with f5(P=0)
    v[i] <- f7(P=0,calls,liste=as.list(environment()))
    c <- c+1
    d <- d+1
  }
  f7(P=0,calls,liste=as.list(environment()))
  print(paste('final call number',calls))
  return(v)
}
f8()

我不确定在R中应该如何完成这个任务。我是否朝着正确的方向前进,特别是在通过CRAN检查时?有人可以给我一些提示吗?

4个回答

31

(1) 传递调用者的环境变量。 您可以明确地将父级环境和其索引传递进去。试试这个:

f2a <- function(P, env = parent.frame()) {
    env$calls <- env$calls + 1
    print(env$calls)
    return(P + env$c + env$d)
}

a <- 1
b <- 2
# same as f1 except f2 removed and call to f2 replaced with call to f2a
f1a <- function(){
    c <- 3
    d <- 4
    calls <- 0
    v <- vector()
    for(i in 1:10){
        v[i] <- f2a(P=0)
        c <- c+1
        d <- d+1
      }
     return(v)
}
f1a()

(2)重置被调用函数的环境 我们可以在f1b中重置f2b的环境,如下所示:

f2b <- function(P) {
    calls <<- calls + 1
    print(calls)
    return(P + c + d)
}

a <- 1
b <- 2
# same as f1 except f2 removed, call to f2 replaced with call to f2b
#  and line marked ## at the beginning is new
f1b <- function(){
    environment(f2b) <- environment() ##
    c <- 3
    d <- 4
    calls <- 0
    v <- vector()
    for(i in 1:10){
        v[i] <- f2b(P=0)
        c <- c+1
        d <- d+1
      }
     return(v)
}
f1b()

(3) 使用 eval.parent(substitute(...)) 的宏 另一种方法是定义类似宏的结构,它有效地将 f2c 的主体内联注入到 f1c1 中。在这里,f2cf2b 相同,除了 calls <- calls + 1 行(不需要 <<-)和整个主体包装在 eval.parent(substitute({...})) 中。f1cf1a 相同,只是对 f2a 的调用被替换为对 f2c 的调用。

f2c <- function(P) eval.parent(substitute({
    calls <- calls + 1
    print(calls)
    return(P + c + d)
}))

a <- 1
b <- 2
f1c <- function(){
    c <- 3
    d <- 4
    calls <- 0
    v <- vector()
    for(i in 1:10){
        v[i] <- f2c(P=0)
        c <- c+1
        d <- d+1
      }
     return(v)
}
f1c()

(4) defmacro 这个解决方案与上一个几乎相同,只是它使用了 gtools 包中的 defmacro 来定义宏,而不是我们自己手动去定义。 (另请参见 Rcmdr 包中的另一种 defmacro 版本。) 由于 defmacro 工作方式的原因,我们还必须传递 calls,但由于它是一个宏而不是函数,这只是告诉它替换 calls,而不是像将 calls 传递给函数那样。

library(gtools)

f2d <- defmacro(P, calls, expr = {
    calls <- calls + 1
    print(calls)
    return(P + c + d)
})

a <- 1
b <- 2
f1d <- function(){
    c <- 3
    d <- 4
    calls <- 0
    v <- vector()
    for(i in 1:10){
        v[i] <- f2d(P=0, calls)
        c <- c+1
        d <- d+1
      }
     return(v)
}
f1d()

1
这给出了我想要的结果,但我没有清楚地表达我的请求。实际上,我想避免索引传递到 f2 中的所有变量,即不写 env$,这就是为什么我尝试使用 with 的原因。我希望能够在 f2 中修改外部的 calls,但不更改当前环境中的其他变量。我将更新问题。 - Zhenglei
看起来正是我所需要的。我将用我的实际案例进行测试,并稍后更新。非常感谢您的帮助! - Zhenglei

2

总的来说,我认为任何需要在函数内使用的变量都应该通过参数传递。此外,如果后续需要使用其值,则应从函数中返回它。不这样做可能会很快导致奇怪的结果,例如,如果有多个函数定义了一个变量 x,应该使用哪个。如果变量数量较大,则可以为其创建自定义数据结构,例如将它们放入命名列表中。


我总体上同意@Paul的看法。 我正在尝试将我的代码打包成一个R软件包,但是无法通过CRAN检查,因为有许多警告,如全局变量绑定等。 由于f2是在一个函数内部定义的,我想在另一个新函数中使用它,因此有很多重复的代码。 我意识到复制粘贴并不是一个好选择,并且可能会在后面的步骤中出现问题。 我还想减少工作量,因为我不想太多地更改已经存在的脚本。 这就是为什么我尝试传递环境而不是定义新的数据结构。 - Zhenglei

1

也可以使用一个函数,在指定的环境中重新定义其他函数。

test_var <- "global"

get_test_var <- function(){
  return(test_var)
}

some_function <- function(){
  test_var <- "local"
  return(get_test_var()) 

}

some_function() # Returns "global". Not what we want here...

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

some_function2 <- function(){
  test_var <- "local"
  # define function locally
  get_test_var2 <- function(){
    return(test_var)
  }
  return(get_test_var2()) 
}

some_function2() # Returns "local", but 'get_test_var2' can't be used in other places.

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

add_function_to_envir <- function(my_function_name, to_envir) {
  script_text <- capture.output(eval(parse(text = my_function_name)))
  script_text[1] <- paste0(my_function_name, " <- ", script_text[1])
  eval(parse(text = script_text), envir = to_envir)
}

some_function3 <- function(){
  test_var <- "local"
  add_function_to_envir("get_test_var", environment()) 
  return(get_test_var()) 
}

some_function3() # Returns "local" and we can use 'get_test_var' from anywhere.

这里的add_function_to_envir(my_function_name, to_envir)函数捕获了函数的脚本,并在新环境中解析并重新评估它。

注意:my_function_name参数中的函数名称需要用引号括起来。


0
每当我使用嵌套函数时,不将变量作为参数传递,而是使用...将它们传递时,我会在所有嵌套函数中使用以下函数从父环境获取变量。
LoadVars <- function(variables, ...){
  for (var in 1:length(variables)) {
    v <- get(variables[var], envir = parent.frame(n=2))
    assign(variables[var], v, envir = parent.frame(n=1))
  }
}

在嵌套函数中,我使用LoadVars(c("foo", "bar"))

这种方法非常有用,因为您只传递所需的变量,就像通过参数传递变量一样。

方法2

但是,很容易重写此函数以从父函数或更高层次加载所有变量,只需将parent.frame中的n值从其原始值2增加即可。

LoadVars <- function(){
  variables <- ls(envir = parent.frame(n=2))

  for (var in 1:length(variables)) {
    v <- get(variables[var], envir = parent.frame(n=2))
    assign(variables[var], v, envir = parent.frame(n=1))
  }
}

例子

a <- 1

A <- function(...){
  b <- 2
  printf("A, a = %s", a)
  printf("A, b = %s", b)
  B()
}

B <- function(...){
  LoadVars()
  printf("B, a = %s", a)
  printf("B, b = %s", b)
}

A()

如果您不在B中加载变量,则B可以加载a,因为它是全局环境变量,但无法加载位于A()中的b

输出:

[1] "A, a = 1"
[1] "A, b = 2"
[1] "B, a = 1"
[1] "B, b = 2"

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