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

Posted

技术标签:

【中文标题】在R中,如何使函数内的变量可用于该函数内的较低级别的函数?(with,attach,environment)【英文标题】:In R, how to make the variables inside a function available to the lower level function inside this function?(with, attach, environment) 【发布时间】:2013-01-02 04:19:13 【问题描述】:

更新 2 @G。 Grothendieck 发布了两种方法。第二个是改变函数内部的函数环境。这解决了我编码重复过多的问题。我不确定这是否是在将我的脚本放入包时通过 CRAN 检查的好方法。等我有结论再更新。

更新

我试图将大量输入参数变量传递给f2,并且不想将函数内的每个变量都索引为env$c, env$d, env$calls,这就是为什么我尝试在f5 和@ 中使用with 987654325@(修改后的f2)。但是,assign 不适用于 with 内,将 assign 移到 with 外将完成这项工作,但在我的实际情况下,我在 with 表达式中有几个 assigns我不知道如何轻松地将它们移出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()

函数f2f1内部,当调用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不要查看全局环境(environment(f4)) ,仅在调用 f3 时查看 environment 内部。

我现在解决的方法是将环境作为列表,使用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 检查时?有人对此有一些提示吗?

【问题讨论】:

【参考方案1】:

(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 内联的主体注入到 @987654326 @。这里f2cf2b 相同,除了calls &lt;- calls + 1 行(不需要&lt;&lt;-)和整个主体包裹在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()

【讨论】:

这给出了我想要的结果,但我没有写清楚我的要求。事实上,我想避免索引所有传入f2的变量,即不写env$,这就是我尝试使用with的原因。我希望能够修改外部calls 内部f2 但不更改当前环境中的其他变量。我会更新问题。 看起来正是我所需要的。我将用我的真实案例对其进行测试并稍后更新。非常感谢您的帮助!【参考方案2】:

每当我使用嵌套函数并且不将变量作为参数传递,而是使用... 传递它们时,我都会在所有嵌套函数中使用以下函数从父环境中获取变量。

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"

【讨论】:

【参考方案3】:

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

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 的函数名称需要用引号引起来。

【讨论】:

【参考方案4】:

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

【讨论】:

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

以上是关于在R中,如何使函数内的变量可用于该函数内的较低级别的函数?(with,attach,environment)的主要内容,如果未能解决你的问题,请参考以下文章

解释啥是全局变量,如何定义

访问javascript中函数内部函数内的变量?

如何在 c 中更改主/另一个函数内的全局结构的值?

如何在训练期间在每个时期修改损失函数内的变量?

C++中如何可以修改const函数内的成员变量的值?

C++中函数参数以及Lambda 函数与表达式