【问题标题】:How to capture a function's environment instead of its return value如何捕获函数的环境而不是其返回值
【发布时间】:2021-06-10 15:19:01
【问题描述】:

给定一个在其临时环境中创建多个对象的函数:

myFun <- function(arg1 = "hey", arg2 = "bye") {
     x <- paste(arg1, arg2)
     y <- paste(x, "hey")
     z <- paste(y, "again!")
     
     return(z)
}

有没有可以提取另一个函数的临时环境的函数? 例如

myFun_env <- capture_env(myFun())

这将作为输出返回:

myFun_env <- rlang::env(
     arg1 = "hey",
     arg2 = "bye",
     x = paste(arg1, arg2),
     y = paste(x, "hey"),
     z = paste(y, "again!")
 )
> rlang::env_print(myFun_env)
<environment: 0x556959d73708>
parent: <environment: global>
bindings:
 * x: <chr>
 * y: <chr>
 * z: <chr>
 * arg1: <chr>
 * arg2: <chr>

更新 1: 来自用户 G. Grothendieck https://stackoverflow.com/users/516548/g-grothendieck的解决方案

> trace(myFun, quote(assign(".Env", environment(), .GlobalEnv)), print = FALSE)
> myFun()
> rlang::env_print(.Env)
<environment: 0x556bf1a8ec08>
parent: <environment: global>
bindings:
 * z: <chr>
 * y: <chr>
 * x: <chr>
 * arg1: <chr>
 * arg2: <chr>
> .Env$arg1
[1] "hey"

这会修改函数的主体,但可以通过 untrace 删除:

> body(myFun)
{
    .doTrace(assign(".Env", environment(), .GlobalEnv))
    {
        x <- paste(arg1, arg2)
        y <- paste(x, "hey")
        z <- paste(y, "again!")
        return(z)
    }
}
> untrace(myFun)
> body(myFun)
{
    x <- paste(arg1, arg2)
    y <- paste(x, "hey")
    z <- paste(y, "again!")
    return(z)
}

>

更新 2: 总结一下,我们可以有一个函数来捕获另一个函数的环境,将其返回并将其写入磁盘以供进一步加载:

captureAndSave_functionEnv <- function (myFun, ...) {
  
  trace(myFun, quote(assign(".myFun_env", environment(), .GlobalEnv)), print = FALSE)
  #trace(myFun, quote({ untrace(myFun); assign(".myFun_env", environment(), parent.frame()) }), print = FALSE)
  on.exit(untrace(myFun))
  
  myFun(...)
  
  saveRDS(.myFun_env, paste0(deparse(substitute(myFun)), ".rds"))
  
  return(.myFun_env)
}
> captureAndSave_functionEnv(myFun)
<environment: 0x558f877eff20>
> myFun_env_loaded <- readRDS("myFun.rds")
> rlang::env_print(myFun_env_loaded)
<environment: 0x558f86d00890>
parent: <environment: global>
bindings:
 * z: <chr>
 * y: <chr>
 * x: <chr>
 * arg1: <chr>
 * arg2: <chr>
> myFun_env_loaded$arg1
[1] "hey"

【问题讨论】:

    标签: r function environment-variables


    【解决方案1】:

    使用trace注入一个捕获环境作为副作用的语句:

    trace(myFun, quote(assign(".Env", environment(), .GlobalEnv)), print = FALSE)
    
    myFun()
    ## [1] "hey bye hey again!"
    
    ls(.Env)
    ## [1] "arg1" "arg2" "x"    "y"    "z"   
    

    【讨论】:

    • 我认为这样做了,那么我们不需要更改函数的定义。不过对我来说这就是黑魔法哈哈。
    • 它实际上确实改变了函数定义。尝试身体(myFun)
    • 确实如此,但是我们可以使用 untrace 来还原它吗?所以也许我们可以有一个函数来执行trace... 然后on.exit 我们执行untrace(myFun)
    • 这将在第一次运行时取消跟踪:trace(myFun, quote({ untrace(myFun); assign(".Env", environment(), .GlobalEnv) }), print = FALSE)
    【解决方案2】:

    函数被调用后,函数的调用环境(它的“栈帧”)不再存在,除非该帧的生命周期通过某种方式被某个地方引用而延长了。1

    所以,不,没有办法事后从外部获取环境。你可以做的是修改函数:

    myFun <- function(arg1 = "hey", arg2 = "bye") {
         x <- paste(arg1, arg2)
         y <- paste(x, "hey")
         z <- paste(y, "again!")
    
         environment()
    }
    

    这里我们返回调用帧而不是z


    1 如果没有明确地这样做,环境通常不会在任何地方引用。但是,也有例外:默认情况下,functionsformulas 都引用了定义它们的环境。因此,如果您在 myFun() 中定义了一个函数或公式,并且返回它,您可以通过在返回值上调用 environment() 来检索 myFun(…) 调用的调用框架。

    【讨论】:

    • 啊,是的,也许我们可以通过函数来​​改变“myFun”的主体?这样我们就不需要在函数定义中包含 environment() ,而是仅在这种情况下将其添加到主体中。
    • @StephGC 没错,我猜:你可以用bquote({.(body(f)); environment()}) 替换body(f)
    • 上下文是有一种简单的方法来存储每个中间对象以在每个函数上构建单元测试(这里它会被粘贴)。可以通过添加函数来完成,例如:``` if (saveOutputForTests) { saveRDS(ls(all.names = TRUE), file = paste0(deparse(sys.calls()[[sys.nframe()-1) ]]), "_environment.rds")) } ``` 但是需要将它添加到我们想要捕获环境的每个函数中。
    【解决方案3】:

    是的,在你的函数中调用 environment 函数。

    f <- function()
    {
        x <- 42
        environment()
    }
    
    r$> f()
    <environment: 0x0000020a1302a5a8>
    r$> f()$x
    [1] 42
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-08
      • 2022-10-14
      • 1970-01-01
      相关资源
      最近更新 更多