В R, как сделать переменные внутри функции доступными для функции нижнего уровня внутри этой функции? (With, attach, environment)
Обновить 2 @G. Гротендик опубликовал два подхода. Второй - изменение функциональной среды внутри функции. Это решает мою проблему слишком большого количества копий. Я не уверен, что это хороший способ пройти проверку CRAN, когда мои скрипты попадают в пакет. Я еще раз обновлюсь, когда у меня появятся некоторые выводы.
Обновить
Я пытаюсь передать много переменных входных аргументов в f2
и не хочу индексировать каждую переменную внутри функции как env$c, env$d, env$calls
, поэтому я пытался использовать with
f5
и f6
( измененный f2
). Однако assign
не работает with
внутри {}
, перемещение assign
снаружи with
будет делать эту работу, но в моем реальном случае у меня есть несколько assign
внутри with
выражениями, которые я не знаю, как переместить их из with
функцией легко,
Вот пример:
## In the <environment: R_GlobalEnv>
a <- 1
b <- 2
f1 <- function(){
c <- 3
d <- 4
f2 <- function(P){
assign("calls", calls+1, inherits=TRUE)
print(calls)
return(P+c+d)
}
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1()
Функция f2
находится внутри f1
, когда вызывается f2
, она ищет calls,c,d
переменных calls,c,d
в среде environment(f1)
. Это то, чего я хотел.
Однако, когда я хочу использовать f2
также в других функциях, я буду определять эту функцию в глобальной среде, назовите ее f4
.
f4 <- function(P){
assign("calls", calls+1, inherits=TRUE)
print(calls)
return(P+c+d)
}
Это не сработает, потому что он будет искать calls,c,d
в глобальной среде, а не внутри функции, где вызывается функция. Например:
f3 <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f4(P=0) ## or replace here with f5(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f3()
Безопасный путь должен определять calls,c,d
во входных аргументах f4
а затем передавать эти параметры в f4
. Однако в моем случае слишком много переменных передается в эту функцию f4
и было бы лучше, если бы я мог передать ее как среду и сказать, что f4
не смотрят в глобальную среду (environment(f4)
), только посмотрите внутри environment
когда вызывается f3
.
Как мне решить эту проблему сейчас, чтобы использовать окружающую среду в виде списка и использовать with
функцией.
f5 <- function(P,liste){
with(liste,{
assign("calls", calls+1, inherits=TRUE)
print(calls)
return(P+c+d)
}
)
}
f3 <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f5(P=0,as.list(environment())) ## or replace here with f5(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f3()
Однако теперь assign("calls", calls+1, inherits=TRUE)
не работают так, как должно быть, поскольку assign
не изменяет исходный объект. Переменные calls
связаны с функцией оптимизации, где целевой функцией является f5
. Именно по этой причине я использую assign
вместо передачи calls
в качестве входных аргументов. Использование attach
также не ясно для меня. Вот мой способ исправить проблему с assign
:
f7 <- function(P,calls,liste){
##calls <<- calls+1
##browser()
assign("calls", calls+1, inherits=TRUE,envir = sys.frame(-1))
print(calls)
with(liste,{
print(paste('with the listed envrionment, calls=',calls))
return(P+c+d)
}
)
}
########
##################
f8 <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
##browser()
##v[i] <- f4(P=0) ## or replace here with f5(P=0)
v[i] <- f7(P=0,calls,liste=as.list(environment()))
c <- c+1
d <- d+1
}
f7(P=0,calls,liste=as.list(environment()))
print(paste('final call number',calls))
return(v)
}
f8()
Я не уверен, как это должно быть сделано в R. Является ли я в правильном направлении, особенно когда вы проходите проверку CRAN? У кого-нибудь есть намеки на это?
Ответы
Ответ 1
(1) Передать зону вызова. Вы можете явно передать родительскую среду и индекс в нее. Попробуй это:
f2a <- function(P, env = parent.frame()) {
env$calls <- env$calls + 1
print(env$calls)
return(P + env$c + env$d)
}
a <- 1
b <- 2
# same as f1 except f2 removed and call to f2 replaced with call to f2a
f1a <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2a(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1a()
(2) Сброс вызываемой функциональной среды - это сброс среды f2b
в f1b
как показано здесь:
f2b <- function(P) {
calls <<- calls + 1
print(calls)
return(P + c + d)
}
a <- 1
b <- 2
# same as f1 except f2 removed, call to f2 replaced with call to f2b
# and line marked ## at the beginning is new
f1b <- function(){
environment(f2b) <- environment() ##
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2b(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1b()
(3) Макрос использованием eval.parent(substitute (...)) Еще один подход заключается в определении макроподобной конструкции, которая эффективно вводит тело f2c
inline в f1c1
. Здесь f2c
совпадает с f2b
за исключением calls <- calls + 1
line (no <<-
needed) и обертывания всего тела в eval.parent(substitute({...}))
. f1c
совпадает с f1a
за исключением того, что вызов f2a
заменяется вызовом f2c
.
f2c <- function(P) eval.parent(substitute({
calls <- calls + 1
print(calls)
return(P + c + d)
}))
a <- 1
b <- 2
f1c <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2c(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1c()
(4) defmacro. Это почти то же самое, что и последнее решение, за исключением того, что в пакете gtools используется defmacro
чтобы определить макрос, а не делать это самостоятельно. (Также см. Пакет Rcmdr для другой версии defmacro.) Из-за того, как работает defmacro
мы также должны передавать calls
но поскольку это макрос, а не функция, это просто говорит, что он заменяет calls
и не совпадает с передачей calls
на функция.
library(gtools)
f2d <- defmacro(P, calls, expr = {
calls <- calls + 1
print(calls)
return(P + c + d)
})
a <- 1
b <- 2
f1d <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2d(P=0, calls)
c <- c+1
d <- d+1
}
return(v)
}
f1d()
Ответ 2
В общем, я бы сказал, что любая переменная, которая нужна внутри функции, должна передаваться через ее аргументы. Кроме того, если его значение необходимо позже, вы передаете его обратно из функции. Не делать этого может довольно быстро привести к странным результатам, например, что, если есть несколько функций, определяющих переменную x
, которую нужно использовать. Если количество переменных больше, вы создаете для него настраиваемую структуру данных, например, помещая их в именованный список.
Ответ 3
Можно также использовать функцию, которая переопределяет другие функции в указанной среде.
test_var <- "global"
get_test_var <- function(){
return(test_var)
}
some_function <- function(){
test_var <- "local"
return(get_test_var())
}
some_function() # Returns "global". Not what we want here...
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
some_function2 <- function(){
test_var <- "local"
# define function locally
get_test_var2 <- function(){
return(test_var)
}
return(get_test_var2())
}
some_function2() # Returns "local", but 'get_test_var2' can't be used in other places.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
add_function_to_envir <- function(my_function_name, to_envir) {
script_text <- capture.output(eval(parse(text = my_function_name)))
script_text[1] <- paste0(my_function_name, " <- ", script_text[1])
eval(parse(text = script_text), envir = to_envir)
}
some_function3 <- function(){
test_var <- "local"
add_function_to_envir("get_test_var", environment())
return(get_test_var())
}
some_function3() # Returns "local" and we can use 'get_test_var' from anywhere.
Здесь add_function_to_envir(my_function_name, to_envir)
захватывает скрипт функции, анализирует и переоценивает ее в новой среде.
Примечание: имя функции для my_function_name
должно быть в кавычках.