R: copy/move one environment to another

前端 未结 7 1996
-上瘾入骨i
-上瘾入骨i 2020-12-14 02:01

I would like to ask if it is possible to copy/move all the objects of one environment to another, at once. For example:

f1 <- function() {
    print(v1)
          


        
相关标签:
7条回答
  • 2020-12-14 02:40

    I use this function in my package to copy objects:

    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)
    }
    
    0 讨论(0)
  • 2020-12-14 02:48

    To do it:

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

    Open the f1 environment and run do this:

    ls(load(f2))
    
    0 讨论(0)
  • 2020-12-14 02:54

    The "clone" method posted by Tommy won't make a true (deep) clone when e1 contains names that reference other environments. For instance, if e1$nestedEnv references an environment, e2$nestedEnv will reference the same environment, not a copy of that environment. Thus, the name e1$nestedEnv$bar will reference the same memory location as e2$nestedEnv$bar and any new value assigned to e1$nestedEnv$bar will be reflected for e2$nestedEnv$bar as well. This may be desirable behavior, but calling e2 a clone of e1 could be misleading.

    Here is a function that will allow the user to make either a copy of an environment while also copying any nested environments (a "deep clone", using deep = TRUE), or just use the method proposed by Tommy to copy the environment while maintaining original references to any nested environments (using deep = FALSE).

    The 'deep = TRUE' method uses rapply to recursively call cloneEnv on nested environment within envir, for as many levels as the environments are nested. So, in the end, it recursively calls rapply, which is a bit of a mind bender, but works pretty well.

    Note that if a nested environment contains a name that references a parent environment, using the "deep" method will never return from the recursive calls. If I could have figured out a way to check for this, I would have included it...

    Note, too, that environments can have attributes, so copying the attributes would be necessary for a true clone, which this solution also addresses.

    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)
    }
    

    An example:

    Create environment e1, which also contains a nested environment:

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

    Show the values for foo and bar:

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

    Make a deep clone (i.e. e2 contains makes a copy of nestedEnv)

    e2 <- cloneEnv(e1, deep = TRUE)
    

    nestedEnv in e1 references a difference environment than nestedEnv in e2:

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

    But the values are the same because e2$nestedEnv is a copy of e1$nestedEnv:

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

    Change the values in e2:

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

    And values in e1 remain unchanged, again, because e1$nestedEnv points to a different environment than e2$nestedEnv:

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

    Now, re-create e1 using Tommy's method:

    e2 <- cloneEnv(e1, deep = FALSE)
    

    nestedEnv in e2 points to the same environment as nestedEnv in e1:

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

    Update the values in e2 and e2's nestedEnv:

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

    Values of foo are independent:

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

    but updating value e2's bar has also updated e1's bar because e1$nestedEnv and e2$nestedEnv reference (point to) the same environment.

    e1$nestedEnv$bar
    [1] "Thanksgiving"
    e2$nestedEnv$bar
    [1] "Thanksgiving"
    
    0 讨论(0)
  • 2020-12-14 02:57

    You could use 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)
      }
    }
    

    If you don't want to list out the objects, ls() takes an environment argument.

    And you'll have to figure out how to get f1.env to be an environment pointing inside f1 :-)

    0 讨论(0)
  • 2020-12-14 02:57

    The other current solutions who actually attempt to make a copy will all fail if the environment contains promises, because they convert environments to lists.

    The solution below works in these cases. Following @geoffrey-poole 's idea I propose an argument to deep copy or not, and I showcase the function on a test case.

    It uses the unexported function is_promise2() from te package {pryr}. I don't know of a base R equivalent.

    The function

    clone_env <- function(env, deep = FALSE) {
      # create new environment with same parent
      clone <- new.env(parent = parent.env(env))
      for(obj in ls(env, all.names = TRUE)) {
        promise_lgl <- pryr:::is_promise2(as.symbol(obj), env = env)
        if(promise_lgl) {
          # fetch promise expression, we use bquote to feed the right unquoted
          # value to substitute
          promise_expr <- eval(bquote(substitute(.(as.symbol(obj)), env = env)))
          # Assign this expression as a promise (delayed assignment) in our
          # cloned environment
          eval(bquote(
            delayedAssign(obj, .(promise_expr), eval.env = 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
    }
    

    Shallow copy

    Let's build an environment containing a character variable, a promise (note that a is undefined), and a nested environment.

    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"
    

    Deep copy

    Let's do it all over again, but with a deep clone now, we see the nested values are distinct this time.

    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"
    

    Created on 2020-09-10 by the reprex package (v0.3.0)

    0 讨论(0)
  • 2020-12-14 03:01

    There seem to be at least 3 different things you can do:

    1. Clone an environment (create an exact duplicate)
    2. Copy the content of one environment to another environment
    3. Share the same environment

    To clone:

    # 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
    

    To copy the content, you can do what @gsk showed. But again, the all.names flag is useful:

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

    To share the environment is what @koshke did. This is probably often much more useful. The result is the same as if creating a local function:

    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 
    
    0 讨论(0)
提交回复
热议问题