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 разные вещи:
- Клонировать среду (создать точную копию)
- Копировать содержимое одной среды в другую среду
- Поделиться в той же среде
Клонировать:
# 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))