web-dev-qa-db-fra.com

Dans R, comment rendre les variables à l'intérieur d'une fonction disponibles pour la fonction de niveau inférieur à l'intérieur de cette fonction? (Avec, attacher, environnement)

Mise à jour 2 @G. Grothendieck a signalé deux approches. La seconde modifie l'environnement de fonction à l'intérieur d'une fonction. Cela résout mon problème de trop de répétitions de codage. Je ne sais pas si c'est une bonne méthode pour passer la vérification CRAN lors de la création de mes scripts dans un package. Je mettrai à jour à nouveau quand j'aurai quelques conclusions.

Mise à jour

J'essaie de passer beaucoup de variables d'argument d'entrée à f2 Et je ne veux pas indexer chaque variable à l'intérieur de la fonction comme env$c, env$d, env$calls, C'est pourquoi j'ai essayé d'utiliser with dans f5 et f6 (un f2 modifié). Cependant, assign ne fonctionne pas avec with à l'intérieur du {}, Déplacer assign à l'extérieur de with fera l'affaire mais dans mon cas réel J'ai quelques assign dans les expressions with que je ne sais pas comment les retirer facilement de la fonction with.

Voici un exemple:

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

La fonction f2 Est à l'intérieur de f1, Lorsque f2 Est appelé, il recherche les variables calls,c,d Dans l'environnement environment(f1). Voilà ce que je voulais.

Cependant, lorsque je veux utiliser f2 Également dans les autres fonctions, je définirai plutôt cette fonction dans l'environnement global, appelez-la f4.

f4 <- function(P){
  assign("calls", calls+1, inherits=TRUE)
  print(calls)
  return(P+c+d)
}

Cela ne fonctionnera pas, car il recherchera calls,c,d Dans l'environnement global au lieu de l'intérieur d'une fonction où la fonction est appelée. Par exemple:

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

Le moyen sûr doit être de définir calls,c,d Dans les arguments d'entrée de f4, Puis de passer ces paramètres dans f4. Cependant, dans mon cas, il y a trop de variables à passer dans cette fonction f4 Et il vaudrait mieux que je puisse le passer comme un environnement et dire à f4 De ne pas regarder dans le Global (environment(f4)), ne regardez à l'intérieur de environment que lorsque f3 est appelé.

La façon dont je le résous maintenant est d'utiliser l'environnement comme une liste et d'utiliser la fonction 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()

Cependant, maintenant assign("calls", calls+1, inherits=TRUE) ne fonctionne pas comme il se doit puisque assign ne modifie pas l'objet d'origine. La variable calls est connectée à une fonction d'optimisation où la fonction objectif est f5. C'est la raison pour laquelle j'utilise assign au lieu de passer calls comme arguments d'entrée. L'utilisation de attach n'est pas non plus claire pour moi. Voici ma façon de corriger le problème 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()

Je ne sais pas comment cela doit être fait dans R. Suis-je dans la bonne direction, surtout lorsque je passe le contrôle CRAN? Quelqu'un a des indices à ce sujet?

26
Zhenglei

(1) Passer l'environnement de l'appelant. Vous pouvez explicitement passer l'environnement parent et y indexer. Essaye ça:

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) Réinitialiser l'environnement de la fonction appelée consiste à réinitialiser l'environnement de f2b Dans f1b Comme indiqué ici:

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) Macro utilisant eval.parent (substitute (...)) Encore une autre approche consiste à définir une construction de type macro qui injecte efficacement le corps de f2c En ligne dans f1c1. Ici, f2c Est identique à f2b À l'exception de la ligne calls <- calls + 1 (Pas de <<- Nécessaire) et de l'habillage du corps entier dans eval.parent(substitute({...})). f1c Est identique à f1a Sauf que l'appel à f2a Est remplacé par un appel à 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 C'est presque la même que la dernière solution sauf qu'elle utilise defmacro dans le paquet gtools pour définir la macro plutôt que de le faire nous-mêmes. (Voir également le package Rcmdr pour une autre version de defmacro.) En raison du fonctionnement de defmacro, nous devons également passer calls mais comme c'est une macro et non une fonction, cela lui dit simplement de remplacer calls in et n'est pas la même chose que de passer calls à une fonction.

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()
25
G. Grothendieck

On pourrait également utiliser une fonction qui redéfinit d'autres fonctions dans l'environnement spécifié.

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.

Ici, add_function_to_envir(my_function_name, to_envir) capture le script de la fonction, l'analyse et le réévalue dans le nouvel environnement.

Remarque: le nom de la fonction pour my_function_name Doit être entre guillemets.

1
Dante Bortone

En général, je dirais que toute variable nécessaire à l'intérieur d'une fonction doit être transmise via ses arguments. De plus, si sa valeur est nécessaire plus tard, vous la renvoyez de la fonction. Ne pas le faire peut conduire assez rapidement à des résultats étranges, par exemple que faire si plusieurs fonctions définissent une variable x, laquelle doit être utilisée. Si le nombre de variables est plus important, vous créez une structure de données personnalisée pour celui-ci, par exemple les mettre dans une liste nommée.

1
Paul Hiemstra

Chaque fois que j'utilise des fonctions imbriquées et ne transmets pas les variables en tant qu'arguments, mais que je les transmets à la place avec ..., J'utilise la fonction suivante dans toutes les fonctions imbriquées pour obtenir des variables de l'environnement parent.

LoadVars <- function(variables, ...){
  for (var in 1:length(variables)) {
    v <- get(variables[var], envir = parent.frame(n=2))
    assign(variables[var], v, envir = parent.frame(n=1))
  }
}

A l'intérieur d'une fonction imbriquée, j'ai ensuite LoadVars(c("foo", "bar")).

Cette approche est utile dans le sens où vous ne transmettez que les variables dont vous avez besoin, comme lorsque vous transmettez les variables via des arguments.

approche 2

Cependant, il est simple de réécrire cette fonction pour charger toutes les variables de la fonction parent - ou plus haut si nécessaire, augmentez simplement le n valeur dans parent.frame à partir de sa valeur d'origine de 2.

LoadVars <- function(){
  variables <- ls(envir = parent.frame(n=2))

  for (var in 1:length(variables)) {
    v <- get(variables[var], envir = parent.frame(n=2))
    assign(variables[var], v, envir = parent.frame(n=1))
  }
}

Exemple

a <- 1

A <- function(...){
  b <- 2
  printf("A, a = %s", a)
  printf("A, b = %s", b)
  B()
}

B <- function(...){
  LoadVars()
  printf("B, a = %s", a)
  printf("B, b = %s", b)
}

A()

Si vous ne chargez pas de variables dans B, alors B est capable de charger a car il s'agit d'une variable d'environnement global, mais pas b qui est situé dans A().

Production:

[1] "A, a = 1"
[1] "A, b = 2"
[1] "B, a = 1"
[1] "B, b = 2"
0
Christian