31

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

更新

我试图将很多输入参数变量传递给f2并且不想将函数内的每个变量索引为env$c, env$d, env$calls,这就是我尝试使用withinf5f6(修改后的f2)的原因。但是,assign不能在with内部使用{},移到assign外部with可以完成这项工作,但在我的实际情况下,我assign在表达式中有一些 s with,我不知道如何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寻找变量。这就是我想要的。calls,c,denvironment(f1)

但是,当我还想f2在其他函数中使用时,我将在 Global 环境中定义这个函数,调用它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()

安全的方式应该calls,c,d在 的输入参数中定义f4,然后将这些参数传递给f4. 但是,在我的例子中,有太多的变量要传递给这个函数f4,我可以将它作为环境传递并告诉f4不要查看全局环境(environment(f4)),只查看调用environmentf3的内部。

我现在解决的方法是把环境当成列表,使用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)因此 now 无法正常工作。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

4 回答 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) 重置被调用函数的环境我们可以重置in的环境,f2b如下f1b所示:

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(...)) 的宏 另一种方法是定义一个类似宏的构造,该构造有效地将f2cinline 的主体注入f1c1. 除了f2c线(不需要)和f2b将整个身体包裹在. 除了调用被替换为对 的调用之外,其他相同。 calls <- calls + 1<<-eval.parent(substitute({...}))f1cf1af2af2c

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 这与最后一个解决方案几乎相同,只是它defmacro在 gtools 包中使用来定义宏而不是自己做。(另见另一个 defmacro 版本的 Rcmdr 包。)由于工作方式,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()
于 2013-01-18T14:37:30.607 回答
2

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

于 2013-01-18T13:59:20.870 回答
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需要用引号引起来。

于 2016-06-26T15:07:20.690 回答
0

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

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")).

这种方法很有用,因为您只传递所需的变量,类似于通过参数传递变量。

方法二

但是,重写此函数以从父函数加载所有变量很简单,或者如果需要更高,只需将nin 的值parent.frame从其原始值增加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,因为它是全局环境变量,但不是b位于A().

输出:

[1] "A, a = 1"
[1] "A, b = 2"
[1] "B, a = 1"
[1] "B, b = 2"
于 2020-03-15T09:56:59.233 回答