【问题标题】:Copy/move one environment to another将一个环境复制/移动到另一个环境
【发布时间】:2012-04-01 15:23:45
【问题描述】:

我想问一下是否可以一次将一个环境的所有对象复制/移动到另一个环境。例如:

f1 <- function() {
    print(v1)
    print(v2)
}

f2 <- function() {
    v1 <- 1
    v2 <- 2

    # environment(f1)$v1 <- v1         # It works
    # environment(f1)$v2 <- v2         # It works
    
    environment(f1) <- environment(f2) # It does not work
}

f2()
f1()

【问题讨论】:

    标签: r


    【解决方案1】:

    您似乎至少可以做 3 种不同的事情:

    1. 克隆环境(创建完全相同的副本)
    2. 将一个环境的内容复制到另一个环境
    3. 共享同一个环境

    要克隆:

    # Make the source env
    e1 <- new.env()
    e1$foo <- 1
    e1$.bar <- 2   # a hidden name
    ls(e1) # only shows "foo"
    
    # This will clone e1
    e2 <- as.environment(as.list(e1, all.names=TRUE))
    
    # Check it...
    identical(e1, e2) # FALSE
    e2$foo
    e2$.bar
    

    要复制内容,您可以执行@gsk 显示的操作。但同样,all.names 标志很有用:

    # e1 is source env, e2 is dest env
    for(n in ls(e1, all.names=TRUE)) assign(n, get(n, e1), e2)
    

    分享环境是@koshke 所做的。这可能通常更有用。结果和创建本地函数一样:

    f2 <- function() {
      v1 <- 1 
      v2 <- 2
    
      # This local function has access to v1 and v2
      flocal <- function() {
        print(v1)
        print(v2)
      }
    
      return(flocal)
    } 
    
    f1 <- f2()
    f1() # prints 1 and 2 
    

    【讨论】:

    • 如果环境包含未评估的承诺,这将不起作用
    【解决方案2】:

    试试这个:

    f2 <- function() {
        v1 <- 1
        v2 <- 2
        environment(f1) <<- environment()
    }
    

    【讨论】:

      【解决方案3】:

      如果环境包含承诺,其他实际尝试复制的当前解决方案都将失败,因为它们将环境转换为列表。

      {rlang} 现在有env_clone(),它将克隆环境,包括承诺和活动绑定。 虽然它不会进行深度复制,并且默认情况下会使用新父级克隆环境,但您可以通过 rlang::env_clone(env, parent.env(env)) 保持相同的父级。

      我推荐上述方法,在下面找到 {rlang} 有更多限制时的先前解决方案。它的行为应该类似,但提供了深拷贝的选项,并在底部给出了警告。


      以下解决方案适用于这些情况。按照@geoffrey-poole 的想法,我提出了一个是否支持深拷贝的论点,并在测试用例中展示了该功能。

      它使用 {pryr} 包中未导出的函数。我不知道基本的 R 等价物。

      函数

      clone_env <- function(env, deep = FALSE) {
        is_unevaled_promise <- function(name, env) {
          pryr:::is_promise2(name, env) && !pryr:::promise_evaled(name, env)
        }
        # create new environment with same parent
        clone <- new.env(parent = parent.env(env))
        for(obj in ls(env, all.names = TRUE)) {
          promise_lgl <- is_unevaled_promise(as.symbol(obj), env = env)
          if(promise_lgl) {
            # fetch promise expression and env
            promise_expr <- pryr:::promise_code(obj, env)
            promise_env  <- pryr:::promise_env(obj, env)
      
            # Assign this expression as a promise (delayed assignment) in our
            # cloned environment
            eval(bquote(
              delayedAssign(obj, .(promise_expr), eval.env = promise_env, assign.env = clone)))
          } else {
            obj_val <- get(obj, envir = env)
            if(is.environment(obj_val) && deep) {
              assign(obj, clone_env(obj_val, deep = TRUE),envir= clone)
            } else  {
              assign(obj, obj_val, envir= clone)
            }
          }
        }
        attributes(clone) <- attributes(env)
        clone
      }
      

      浅拷贝

      让我们构建一个包含字符变量、promise(注意a 未定义)和嵌套环境的环境。

      create_test_env <- function(x = a){
        y <- "original"
        nested_env <- new.env()
        nested_env$nested_value <- "original"
        environment()
      }
      env <- create_test_env()
      ls(env)
      #> [1] "nested_env" "x"          "y"
      
      # clone it, with deep = FALSE
      shallow_clone <- clone_env(env, deep = FALSE) 
      #> Registered S3 method overwritten by 'pryr':
      #>   method      from
      #>   print.bytes Rcpp
      ls(shallow_clone)
      #> [1] "nested_env" "x"          "y"
      
      # the promise was copied smoothly
      a <- 42
      shallow_clone$x
      #> [1] 42
      
      # We can change values independently
      shallow_clone$y <- "modified"
      env$y
      #> [1] "original"
      
      # except if we have nested environents!
      shallow_clone$nested_env$nested_value <- "modified"
      env$nested_env$nested_value
      #> [1] "modified"
      

      深拷贝

      让我们重新做一遍,但是现在有了深度克隆,我们看到这次嵌套的值是不同的。

      env <- create_test_env()
      deep_clone <- clone_env(env, deep = TRUE) 
      a <- 42
      deep_clone$x
      #> [1] 42
      deep_clone$y <- "modified"
      env$y
      #> [1] "original"
      deep_clone$nested_env$nested_value <- "modified"
      env$nested_env$nested_value
      #> [1] "original"
      

      由reprex package (v0.3.0) 于 2020 年 9 月 10 日创建


      请注意,深拷贝不是超级健壮:

      • 我们应该递归地探索每个列表对象,并寻找可以在那里克隆的环境。
      • 如果我们在那里找到.GlobalEnv、.BaseNamespaceEnv 等,我们真的要克隆它们吗?我们可以有一个参数来提供不克隆的参数
      • 属性也可能包含环境,这些不是深度复制的

      我还没有满足在兔子洞里走得更远的需要。我需要您随时编辑或复制并改进您自己的答案。

      【讨论】:

        【解决方案4】:

        你可以使用assign:

        f1 <- function() {
          print(v1)
          print(v2)
        }
        
        f2 <- function() {
          v1 <- 1
          v2 <- 2
        
          for(obj in c("v1","v2")) {
            assign(obj,get(obj),envir=f1.env)
          }
        }
        

        如果您不想列出对象,ls() 采用环境参数。

        你必须弄清楚如何让 f1.env 成为指向 f1 内部的环境:-)

        【讨论】:

        • 谢谢!这是一个有趣的问题。 @kohske 的解决方案似乎更优雅(一如既往!)。
        • @kohske 哦哦。也许,envir 选项并没有像我想的那样做。我再看看。
        • @gsk3 可能 envir 选项符合您的想法,但 environment(f1) 是 .GlobalEnv。
        • 也许更复杂的事情是将变量的内存引用的副本而不是变量本身传递给其他环境。
        • 对于它的价值,这个答案似乎与我在 R 核心成员编写的 R 代码中看到的内部完成方式相匹配......所以它可能是“规范的”。 @kohske 更聪明,但这让我很紧张。
        【解决方案5】:

        当e1 包含引用其他环境的名称时,Tommy 发布的“克隆”方法不会进行真正的(深度)克隆。例如,如果e1$nestedEnv 引用一个环境,e2$nestedEnv 将引用相同的环境,而不是该环境的副本。因此,名称e1$nestedEnv$bar 将引用与e2$nestedEnv$bar 相同的内存位置,并且分配给e1$nestedEnv$bar 的任何新值也将反映给e2$nestedEnv$bar。这可能是可取的行为,但将e2 称为e1 的克隆可能会产生误导。

        这是一个功能,它允许用户在复制环境的同时复制任何嵌套环境(“深度克隆”,使用 deep = TRUE),或者只使用 Tommy 提出的方法来复制环境,同时保持对任何嵌套环境的原始引用(使用deep = FALSE)。

        'deep = TRUE' 方法使用rapply 在envir 内的嵌套环境中递归调用cloneEnv,因为嵌套的级别与嵌套的环境一样多。所以,最后,它递归地调用rapply,这有点让人费解,但效果很好。

        请注意,如果嵌套环境包含引用父环境的名称,则使用“深度”方法将永远不会从递归调用中返回。如果我能想出一种方法来检查这个,我会把它包括在内......

        还要注意,环境可以具有属性,因此复制属性对于真正的克隆是必要的,此解决方案也解决了这一问题。

        cloneEnv <- function(envir, deep = T) {
          if(deep) {
            clone <- list2env(rapply(as.list(envir, all.names = TRUE), cloneEnv, classes = "environment", how = "replace"), parent = parent.env(envir))
          } else {
            clone <- list2env(as.list(envir, all.names = TRUE), parent = parent.env(envir))
          }
          attributes(clone) <- attributes(envir)
          return(clone)
        }
        

        一个例子:

        创建环境e1,其中还包含一个嵌套环境:

        e1 <- new.env()
        e1$foo <- "Christmas"
        e1$nestedEnv <- new.env()
        e1$nestedEnv$bar <- "New Years"
        

        显示foo 和bar 的值:

        e1$foo
        [1] "Christmas"
        e1$nestedEnv$bar
        [1] "New Years"
        

        进行深度克隆(即e2 包含复制nestedEnv)

        e2 <- cloneEnv(e1, deep = TRUE)
        

        e1 中的nestedEnv 引用了与e2 中的nestedEnv 不同的环境:

        identical(e1$nestedEnv, e2$nestedEnv)
        [1] FALSE
        

        但值是相同的,因为e2$nestedEnv 是e1$nestedEnv 的副本:

        e2$foo
        [1] "Christmas"
        e2$nestedEnv$bar
        [1] "New Years"
        

        更改e2中的值:

        e2$foo <- "Halloween"
        e2$nestedEnv$bar <- "Thanksgiving"
        

        e1 中的值再次保持不变,因为e1$nestedEnv 指向的环境与e2$nestedEnv 不同:

        e1$foo
        [1] "Christmas"
        e2$foo
        [1] "Halloween"
        
        e1$nestedEnv$bar
        [1] "New Years"
        e2$nestedEnv$bar
        [1] "Thanksgiving"
        

        现在,使用 Tommy 的方法重新创建 e1:

        e2 <- cloneEnv(e1, deep = FALSE)
        

        e2 中的nestedEnv 与e1 中的nestedEnv 指向相同的环境:

        identical(e1$nestedEnv, e2$nestedEnv)
        [1] TRUE
        

        更新e2 和e2 的nestedEnv 中的值:

        e2$foo <- "Halloween"
        e2$nestedEnv$bar <- "Thanksgiving"
        

        foo 的值是独立的:

        e1$foo
        [1] "Christmas"
        e2$foo
        [1] "Halloween"
        

        但更新值e2 的bar 也更新了e1 的bar,因为e1$nestedEnv 和e2$nestedEnv 引用(指向)相同的环境。

        e1$nestedEnv$bar
        [1] "Thanksgiving"
        e2$nestedEnv$bar
        [1] "Thanksgiving"
        

        【讨论】:

        • 如果环境包含未评估的承诺,这也会失败。
        【解决方案6】:

        我在我的包中使用这个函数来复制对象:

        copyEnv <- function(from, to, names=ls(from, all.names=TRUE)) {
          mapply(assign, names, mget(names, from), list(to), 
                 SIMPLIFY = FALSE, USE.NAMES = FALSE)
          invisible(NULL)
        }
        

        【讨论】:

          【解决方案7】:

          这样做:

          environment(f1) <- environment(f2) # It does not work
          

          打开f1 环境并运行:

          ls(load(f2))
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-07-05
            • 2020-02-12
            • 1970-01-01
            • 2021-11-24
            • 1970-01-01
            • 2018-05-30
            • 2022-06-14
            • 1970-01-01
            相关资源
            最近更新 更多