Obtenez toutes les fonctions d'origine

11

Dans R, j'utilise source()pour charger certaines fonctions:

source("functions.R")

Est-il possible d'obtenir la liste de toutes les fonctions définies dans ce fichier? En tant que noms de fonction. (Peut source()- être que lui - même peut le rendre en quelque sorte?).

PS: Le dernier recours serait d'appeler une source()deuxième fois comme local({ source(); })et ensuite de faire des fonctions ls()internes et de filtrage, mais c'est trop compliqué - y a-t-il une solution plus facile et moins maladroite?

TMS
la source
1
Cela n'utilise pas source(), mais ce vieux fil peut vous intéresser.
Andrew
1
@Andrew merci, j'ai vérifié les solutions proposées mais cela semble beaucoup plus fou que le dernier recours que j'ai présenté dans la question :)
TMS
2
Je ne sais pas, cette solution est plus folle:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris
2
Créez un package à partir de vos fichiers source. Ensuite, vous obtenez tous les avantages, y compris un espace de noms de package.
Roland
@TMS, vous avez mal compris votre question / je n'ai pas lu que vous vouliez des fonctions définies . Mes excuses!
Andrew

Réponses:

7

Je pense que la meilleure façon serait de placer le fichier dans un environnement temporaire. Recherchez cet environnement pour toutes les fonctions, puis copiez ces valeurs dans l'environnement parent.

my_source <- function(..., local=NULL) {
  tmp <- new.env(parent=parent.frame())
  source(..., local = tmp)
  funs <- names(tmp)[unlist(eapply(tmp, is.function))]
  for(x in names(tmp)) {
    assign(x, tmp[[x]], envir = parent.frame())
  }
  list(functions=funs)
}

my_source("script.R")
MrFlick
la source
merci, cette solution semble prometteuse, comme la seule en ce moment! Étonnamment, celui qui a le moins de votes positifs. C'est celui que j'ai mentionné en dernier recours, mais en utilisant new.env()au lieu de l'élégant local({ })que je ne sais pas si cela fonctionnerait avec le assigncadre parent.
TMS
1) pensez-vous que cela fonctionnerait local()? Et BTW, 2) ce que vous faites dans la boucle for: n'y a-t-il pas de fonction pour fusionner les environnements?
TMS
1
@TMS Cela pourrait fonctionner avec local, même si je ne l'ai pas essayé. Je ne connais pas d'autre moyen de copier toutes les variables d'un environnement à un autre. Ce n'est pas une opération courante.
MrFlick
Je pense que attachpeut être utilisé pour, bien, attacher un environnement à un autre. Bien que vous deviez utiliser l' posargument plutôt que de spécifier le parent.frame. Et cela ne fonctionnera bien que pour copier tout l'environnement, la forboucle de MrFlick vous permet de copier uniquement les fonctions.
Gregor Thomas
5

C'est un peu maladroit mais vous pouvez regarder les changements dans les objets avant et après l' sourceappel comme ça.

    # optionally delete all variables
    #rm(list=ls())

    before <- ls()
    cat("f1 <- function(){}\nf2 <- function(){}\n", file = 'define_function.R')
    # defines these
    #f1 <- function() {}
    #f2 <- function() {}
    source('define_function.R')
    after <- ls()

    changed <- setdiff(after, before)
    changed_objects <- mget(changed, inherits = T)
    changed_function <- do.call(rbind, lapply(changed_objects, is.function))
    new_functions <- changed[changed_function]

    new_functions
    # [1] "f1" "f2"
Andrew Chisholm
la source
Merci! J'ai également eu cette idée mais cela ne fonctionne pas pour une raison très simple - si le paquet était déjà chargé (ce qui arrive tout le temps lorsque je débogue le code, je ressource juste les sources), alors il ne retourne rien.
TMS
3

Je pense que cette expression régulière capture presque tous les types de fonctions valides (opérateur binaire, fonctions d'affectation) et tous les caractères valides dans un nom de fonction, mais j'ai peut-être manqué un cas de bord.

# lines <- readLines("functions.R")

lines <- c(
  "`%in%` <- function",
  "foo <- function",
  "foo2bar <- function",
  "`%in%`<-function",
  "foo<-function",
  ".foo <-function",
  "foo2bar<-function",
  "`foo2bar<-`<-function",
  "`foo3bar<-`=function",
  "`foo4bar<-` = function",
  "` d d` <- function", 
  "lapply(x, function)"
)
grep("^`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?\\s*(<-|=)\\s*function", lines)
#>  [1]  1  2  3  4  5  6  7  8  9 10
funs <- grep("^`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?\\s*(<-|=)\\s*function", lines, value = TRUE)
gsub("^(`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?).*", "\\1", funs)
#>  [1] "`%in%`"      "foo "        "foo2bar "    "`%in%`"      "foo"        
#>  [6] ".foo "       "foo2bar"     "`foo2bar<-`" "`foo3bar<-`" "`foo4bar<-`"
Alan ocallaghan
la source
1
fyi Je pense que ce n'est pas vraiment une bonne solution mais c'est définitivement une solution amusante . Je convertirais probablement le fichier en un package si j'avais vraiment besoin de ces informations.
Alan Ocallaghan du
J'ai raté deux cas de bord! Les fonctions peuvent commencer par .et les fonctions d'assignation ( `foo<-`<- function(x, value)existent.
Alan Ocallaghan
J'utilise =pour l'affectation, cela n'attrapera aucune de mes fonctions ...
Gregor Thomas
Bonne prise - édité. Je noterai que R vous permet de faire des choses idiotes comme ` d d` <- function(x)ce qui n'est actuellement pas pris. Je ne veux pas que l'expression régulière devienne trop idiote, bien que je puisse y revenir.
Alan Ocallaghan du
, Vous pouvez également assigner des fonctions avec assign, <<-et ->. Et il sera très difficile de faire en sorte que cette approche prenne en compte les fonctions définies dans les fonctions, mais qui ne se trouvent pas réellement dans l'environnement d'origine. Votre réponse devrait très bien fonctionner pour les cas standard, mais vous ne voulez pas réellement écrire un analyseur R hors regex.
Gregor Thomas
1

S'il s'agit de votre propre script afin que vous ayez le contrôle sur la façon dont il est formaté, une simple convention serait suffisante. Assurez-vous simplement que chaque nom de fonction commence au premier caractère de sa ligne et que le mot functionapparaît également sur cette ligne. Toute autre utilisation du mot functiondoit apparaître sur une ligne commençant par un espace ou une tabulation. Ensuite, une solution en ligne est:

sub(" .*", "", grep("^\\S.*function", readLines("myscript.R"), value = TRUE))

Les avantages de cette approche sont que

  • c'est très simple . Les règles sont simplement énoncées et il n'y a qu'une seule ligne simple de code R nécessaire pour extraire les noms de fonction. Regex est également simple et pour un fichier existant, il est très facile à vérifier - il suffit de grep le mot functionet de vérifier si chaque occurrence affichée suit la règle.

  • pas besoin d'exécuter la source. C'est entièrement statique .

  • dans de nombreux cas, vous n'aurez pas du tout besoin de modifier le fichier source et dans d'autres, les modifications seront minimes. Si vous écrivez le script à partir de zéro dans cet esprit, il est encore plus facile à organiser.

Il existe de nombreuses autres alternatives à l'idée de conventions. vous pouvez avoir une expression rationnelle plus sophistiquée ou vous pouvez ajouter # FUNCTIONà la fin de la première ligne de toute définition de fonction si vous écrivez le script à partir de zéro, puis repérez cette phrase et extrayez le premier mot de la ligne, mais la principale suggestion semble ici particulièrement attrayant en raison de sa simplicité et des autres avantages énumérés.

Tester

# generate test file
cat("f <- function(x) x\nf(23)\n", file = "myscript.R") 

sub(" .*", "", grep("^\\S.*function", readLines("myscript.R"), value = TRUE))
## [1] "f"
G. Grothendieck
la source
lapply(x, function(y) dostuff(y))briserait cela
alan ocallaghan
@alan ocallaghan, Votre exemple viole les règles énoncées et ne peut donc pas se produire valablement. Pour écrire ceci et rester dans les règles, il faudrait démarrer la fonction sur une nouvelle ligne en retrait ou on devrait mettre en retrait le lapply.
G. Grothendieck
Je pense que l'utilitaire est massivement dégradé si vous avez besoin d'un formatage spécifique, car cela pourrait nécessiter de changer le fichier - dans ce cas, vous pouvez également suggérer à l'utilisateur de lire les noms de fonction manuellement
alan ocallaghan
1
Ce n'est qu'une considération si vous ne contrôlez pas le fichier, mais nous avons exclu cette possibilité. L'utilisation de conventions est très courante en programmation. Je mets souvent # TODOtout au long de mon code afin que je puisse exécuter mes tâches, par exemple. Une autre possibilité dans le même sens serait d'écrire # FUNCTIONà la fin de la première ligne de toute définition de fonction.
G. Grothendieck
1
essayer d'analyser avec regex est la route de l'enfer ....
TMS
0

Cela adapte le code utilisé dans la publication de mon commentaire pour rechercher une séquence de jetons (symbole, opérateur d'affectation, puis fonction), et il devrait saisir toutes les fonctions définies. Je ne sais pas si c'est robuste comme réponse de MrFlick, mais c'est une autre option:

source2 <- function(file, ...) {
  source(file, ...)
  t_t <- subset(getParseData(parse(file)), terminal == TRUE)
  subset(t_t, token == "SYMBOL" & 
           grepl("ASSIGN", c(tail(token, -1), NA), fixed = TRUE) & 
           c(tail(token, -2), NA, NA) == "FUNCTION")[["text"]]
}
Andrew
la source