Holen Sie sich alle Funktionen

11

In R source()lade ich einige Funktionen:

source("functions.R")

Ist es möglich, die Liste aller in dieser Datei definierten Funktionen abzurufen? Als Funktionsnamen. (Vielleicht source()kann es selbst irgendwie zurückgeben?).

PS: Der letzte Ausweg wäre, das source()zweite Mal aufzurufen local({ source(); })und dann ls()Innen- und Filterfunktionen auszuführen, aber das ist zu kompliziert - gibt es eine einfachere und weniger ungeschickte Lösung?

TMS
quelle
1
Dies wird nicht verwendet source(), aber dieser alte Thread könnte für Sie von Interesse sein.
Andrew
1
@ Andrew danke, ich habe die vorgeschlagenen Lösungen überprüft, aber das klingt viel verrückter als der letzte Ausweg, den ich in der Frage vorgestellt habe :)
TMS
2
Ich weiß nicht, dass diese Lösung verrückter ist:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris
2
Machen Sie ein Paket aus Ihren Quelldateien. Dann erhalten Sie alle Vorteile einschließlich eines Paket-Namespace.
Roland
@TMS, habe deine Frage falsch verstanden / nicht gelesen, dass du definierte Funktionen haben willst . Entschuldigung!
Andrew

Antworten:

7

Ich denke, der beste Weg wäre, die Datei in eine temporäre Umgebung zu bringen. Fragen Sie diese Umgebung nach allen Funktionen ab und kopieren Sie diese Werte in die übergeordnete Umgebung.

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
quelle
danke, diese Lösung sieht vielversprechend aus, als die einzige im Moment! Überraschenderweise derjenige mit den wenigsten positiven Stimmen. Es ist das, was ich als letztes Mittel erwähnt habe, aber new.env()anstelle des eleganten, bei local({ })dem ich nicht sicher bin, ob es mit assigndem übergeordneten Frame funktionieren würde .
TMS
1) Glaubst du, es würde funktionieren local()? Und übrigens, 2) was Sie in der for-Schleife tun: Gibt es keine Funktion zum Zusammenführen von Umgebungen?
TMS
1
@TMS Es könnte mit lokal funktionieren, obwohl ich es nicht versucht habe. Mir ist keine andere Möglichkeit bekannt, alle Variablen von einer Umgebung in eine andere zu kopieren. Es ist keine übliche Operation.
MrFlick
Ich denke, attachkann verwendet werden, um eine Umgebung mit einer anderen zu verbinden. Sie müssen jedoch das posArgument verwenden, anstatt das anzugeben parent.frame. Und es funktioniert nur gut zum Kopieren der gesamten Umgebung. Mit der MrFlick- forSchleife können Sie nur die Funktionen kopieren.
Gregor Thomas
5

Es ist etwas klobig, aber Sie können Änderungen an den Objekten vor und nach dem sourceAufruf wie folgt betrachten.

    # 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
quelle
Vielen Dank! Ich hatte diese Idee auch, aber sie funktioniert aus sehr einfachen Gründen nicht - wenn das Paket bereits geladen war (was die ganze Zeit passiert, wenn ich den Code debugge, gebe ich nur die Quellen neu), gibt es nichts zurück.
TMS
3

Ich denke, dass dieser reguläre Ausdruck fast jeden gültigen Funktionstyp (Binäroperator, Zuweisungsfunktionen) und jedes gültige Zeichen in einem Funktionsnamen erfasst, aber ich habe möglicherweise einen Randfall übersehen.

# 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
quelle
1
Zu Ihrer Information, ich denke, das ist keine wirklich gute Lösung, aber es ist definitiv eine lustige Lösung. Ich würde die Datei wahrscheinlich in ein Paket konvertieren, wenn ich diese Informationen wirklich benötige.
Alan Ocallaghan
Ich habe zwei Randfälle verpasst! Funktionen können mit .und Zuweisungsfunktionen beginnen ( `foo<-`<- function(x, value)vorhanden.
Alan Ocallaghan
Ich benutze =für die Zuweisung, dies wird keine meiner Funktionen
Gregor Thomas
Guter Fang - bearbeitet. Ich werde bemerken, dass Sie mit R dumme Dinge tun können, wie sie ` d d` <- function(x)derzeit nicht gefangen werden. Ich möchte nicht, dass der reguläre Ausdruck zu albern wird, obwohl ich ihn vielleicht noch einmal besuchen werde.
Alan Ocallaghan
Auch könnten Sie Funktionen zuweisen mit assign, <<-und ->. Und es wird sehr schwierig sein, diesen Ansatz für Funktionen zu berücksichtigen, die innerhalb von Funktionen definiert sind, sich jedoch nicht in der Sourcing-Umgebung befinden. Ihre Antwort sollte in Standardfällen sehr gut funktionieren, aber Sie möchten eigentlich keinen R-Parser aus Regex schreiben.
Gregor Thomas
1

Wenn dies Ihr eigenes Skript ist, damit Sie die Kontrolle über die Formatierung haben, ist eine einfache Konvention ausreichend. Stellen Sie einfach sicher, dass jeder Funktionsname beim ersten Zeichen in seiner Zeile beginnt und dass das Wort functionauch in dieser Zeile erscheint. Jede andere Verwendung des Wortes functionsollte in einer Zeile erscheinen, die mit einem Leerzeichen oder einer Registerkarte beginnt. Dann ist eine einzeilige Lösung:

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

Die Vorteile dieses Ansatzes sind die folgenden

  • es ist sehr einfach . Die Regeln werden einfach angegeben und es wird nur eine einfache Zeile R-Code benötigt, um die Funktionsnamen zu extrahieren. Regex ist auch einfach und für eine vorhandene Datei sehr einfach zu überprüfen - greifen Sie einfach nach dem Wort functionund prüfen Sie, ob jedes angezeigte Vorkommen der Regel entspricht.

  • Die Quelle muss nicht ausgeführt werden. Es ist völlig statisch .

  • In vielen Fällen müssen Sie die Quelldatei überhaupt nicht ändern, in anderen Fällen werden nur minimale Änderungen vorgenommen. Wenn Sie das Skript in diesem Sinne von Grund auf neu schreiben, ist es noch einfacher zu arrangieren.

Es gibt viele andere Alternativen entlang der Idee von Konventionen. Sie könnten einen komplexeren regulären Ausdruck haben oder # FUNCTIONam Ende der ersten Zeile einer Funktionsdefinition hinzufügen, wenn Sie das Skript von Grund auf neu schreiben und dann diesen Satz herausgreifen und das erste Wort in der Zeile extrahieren, aber der Hauptvorschlag hier scheint besonders attraktiv aufgrund seiner Einfachheit und der anderen aufgeführten Vorteile.

Prüfung

# 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
quelle
lapply(x, function(y) dostuff(y))würde dies brechen
Alan Ocallaghan
@alan ocallaghan, Ihr Beispiel verstößt gegen die angegebenen Regeln, sodass es nicht gültig auftreten kann. Um dies zu schreiben und trotzdem innerhalb der Regeln zu bleiben, müsste man die Funktion in einer neuen Zeile starten, die eingerückt ist, oder man könnte das Lapply einrücken.
G. Grothendieck
Ich denke, das Dienstprogramm wird massiv beeinträchtigt, wenn Sie eine bestimmte Formatierung benötigen, da dies möglicherweise eine Änderung der Datei erfordert. In diesem Fall können Sie dem Benutzer auch vorschlagen, die Funktionsnamen manuell zu lesen
Alan Ocallaghan,
1
Dies ist nur dann eine Überlegung, wenn Sie die Datei nicht kontrollieren, aber wir haben diese Möglichkeit ausgeschlossen. Die Verwendung von Konventionen ist in der Programmierung sehr verbreitet. Ich # TODOfüge oft meinen Code ein, damit ich zum Beispiel meine Aufgaben erledigen kann. Eine andere Möglichkeit in der gleichen Richtung wäre, # FUNCTIONam Ende der ersten Zeile einer Funktionsdefinition zu schreiben .
G. Grothendieck
1
Der Versuch, mit Regex zu analysieren, ist der Weg zur Hölle ...
TMS
0

Dadurch wird der im Beitrag verwendete Code aus meinem Kommentar angepasst, um nach einer Folge von Token (Symbol, Zuweisungsoperator, dann Funktion) zu suchen, und es sollten alle definierten Funktionen erfasst werden. Ich bin nicht sicher, ob es als Antwort von MrFlick robust ist, aber es ist eine andere 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
quelle