R: копировать / перемещать одну среду в другую

Я хотел бы спросить, возможно ли одновременно копировать / перемещать все объекты одной среды в другую. Например:

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

TNX, заранее

6 ответов

Решение

Кажется, вы можете сделать как минимум 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 

Попробуй это:

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

Все другие текущие решения, которые фактически пытаются сделать копию, потерпят неудачу, если среда содержит обещания, потому что они преобразуют среды в списки.

Приведенное ниже решение работает в этих случаях. Следуя идее @geoffrey-poole, я предлагаю аргумент в пользу глубокого копирования или нет, и демонстрирую функцию на тестовом примере.

Он использует неэкспортированную функцию is_promise2()из пакета {прыр}. Я не знаю эквивалента в базе R.

Функция

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
}

Мелкая копия

Давайте создадим среду, содержащую символьную переменную, обещание (обратите внимание, что 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"

Создано 10.09.2020 с помощью пакета REPEX (v0.3.0)

Метод "клонирования", опубликованный Томми, не сделает истинного (глубокого) клона, когда e1 содержит имена, которые ссылаются на другие среды. Например, если e1$nestedEnv ссылается на окружающую среду, e2$nestedEnv будет ссылаться на ту же среду, а не на копию этой среды. Таким образом, имя e1$nestedEnv$bar будет ссылаться на ту же область памяти, что и e2$nestedEnv$bar и любое новое значение, присвоенное e1$nestedEnv$bar будет отражено для e2$nestedEnv$bar также. Это может быть желательным поведением, но вызов e2 клон e1 может вводить в заблуждение.

Вот функция, которая позволит пользователю сделать копию среды, а также скопировать любые вложенные среды ("глубокий клон", используя deep = TRUE) или просто используйте метод, предложенный Томми, для копирования среды, сохраняя при этом исходные ссылки на любые вложенные среды (используя deep = FALSE).

Метод deep = TRUE использует rapply рекурсивно вызывать cloneEnv на вложенной среде внутри envirна столько же уровней, сколько вложенные среды. Итак, в конце концов, это рекурсивно вызывает 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)

nestedEnv в e1 ссылается на разницу среды, чем nestedEnv в e2:

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"

Теперь воссоздайте e1 используя метод Томми:

e2 <- cloneEnv(e1, deep = FALSE)

nestedEnv в e2 указывает на ту же среду, что и nestedEnv в e1:

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

Обновите значения в e2 а также e2"s nestedEnv:

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

Значения foo независимы:

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

но обновляя значение e2"s bar также обновил e1"s bar так как e1$nestedEnv а также e2$nestedEnv ссылка (указать) на ту же среду.

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

Вы можете использовать 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:-)

Я использую эту функцию в своем пакете для копирования объектов:

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

Сделать это:

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

Открой f1 Окружающая среда и запустить сделать это:

ls(load(f2))
Другие вопросы по тегам