Uzyskaj wszystkie pozyskane funkcje

11

W R używam source()do załadowania niektórych funkcji:

source("functions.R")

Czy można uzyskać listę wszystkich funkcji zdefiniowanych w tym pliku? Jako nazwy funkcji. (Możesource() sam może to jakoś zwrócić?).

PS: Ostatnim rozwiązaniem byłoby wywoływanie source()po raz drugi jak, local({ source(); })a następnie wykonywanie funkcji ls()wewnętrznych i filtrowanie, ale to zbyt skomplikowane - czy jest łatwiejsze i mniej niezręczne rozwiązanie?

TMS
źródło
1
Nie używa source(), ale ten stary wątek może Cię zainteresować.
Andrew
1
@ Dziękuję, sprawdziłem proponowane rozwiązania, ale brzmi to bardziej szalone niż ostatnia deska ratunku, którą przedstawiłem w pytaniu :)
TMS
2
Nie wiem, czy to rozwiązanie jest bardziej szalone:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris
2
Stwórz pakiet ze swoich plików źródłowych. Otrzymasz wszystkie zalety, w tym przestrzeń nazw pakietu.
Roland
@TMS, źle zrozumiałem twoje pytanie / nie przeczytałem, że chcesz zdefiniowane funkcje. Przeprosiny!
Andrew,

Odpowiedzi:

7

Myślę, że najlepszym sposobem byłoby źródło pliku w tymczasowym środowisku. Zapytaj to środowisko dla wszystkich funkcji, a następnie skopiuj te wartości do środowiska nadrzędnego.

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
źródło
dzięki, to rozwiązanie wygląda obiecująco, jako jedyne w tej chwili! O dziwo, ten z najmniej pozytywnymi opiniami. To ten, o którym wspomniałem jako ostateczność, ale new.env()zamiast eleganckiego, local({ })którego nie jestem pewien, czy zadziała z assignramką nadrzędną.
TMS
1) czy sądzisz, że to zadziała local()? I BTW, 2) co robisz w pętli for: czy nie ma funkcji scalania środowisk?
TMS
1
@TMS Może to działać z lokalnym, choć nie próbowałem. Nie znam innego sposobu kopiowania wszystkich zmiennych z jednego środowiska do drugiego. To nie jest powszechna operacja.
MrFlick
Myślę, że attachmożna go wykorzystać do połączenia jednego środowiska z drugim. Chociaż musisz użyć posargumentu zamiast określać parent.frame. I będzie działał dobrze tylko do kopiowania całego środowiska, forpętla MrFlick pozwala kopiować tylko funkcje.
Gregor Thomas
5

Jest to trochę niezgrabne, ale możesz zobaczyć zmiany w obiektach przed i po sourcewywołaniu w ten sposób.

    # 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
źródło
Dzięki! Miałem też ten pomysł, ale nie działa z bardzo prostego powodu - jeśli pakiet został już załadowany (co zdarza się przez cały czas, gdy debuguję kod, po prostu ponownie źródła źródła), to nic nie zwraca.
TMS
3

Myślę, że to wyrażenie regularne przechwytuje prawie każdy poprawny typ funkcji (operator binarny, funkcje przypisania) i każdy poprawny znak w nazwie funkcji, ale mogłem nie zauważyć wielkości krawędzi.

# 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
źródło
1
fyi Myślę, że to nie jest dobre rozwiązanie, ale zdecydowanie jest to fajne rozwiązanie. Prawdopodobnie przekonwertowałbym plik na pakiet, gdybym naprawdę potrzebował tych informacji.
alan ocallaghan
Brakowało mi dwóch skrzyń! Funkcje mogą zaczynać się od .i funkcje przypisania ( `foo<-`<- function(x, value)istnieją.
alan ocallaghan
Używam =do przypisania, to nie złapie żadnej z moich funkcji ...
Gregor Thomas
Dobry haczyk - zredagowany. Zauważę, że R pozwala ci robić głupie rzeczy, takie jak ` d d` <- function(x)które nie są obecnie łapane. Nie chcę, aby wyrażenie regularne stało się zbyt głupie, chociaż mogę wrócić.
alan ocallaghan
Ponadto, można przypisać funkcje z assign, <<-, i ->. Bardzo trudno będzie uczynić to podejście rachunkiem dla funkcji, które są zdefiniowane w ramach funkcji, ale tak naprawdę nie znajdują się w środowisku źródłowym. Twoja odpowiedź powinna działać bardzo dobrze w standardowych przypadkach, ale tak naprawdę nie chcesz pisać parsera R z wyrażenia regularnego.
Gregor Thomas
1

Jeśli jest to twój własny skrypt, który pozwala kontrolować sposób jego formatowania, wystarczy zwykła konwencja. Upewnij się tylko, że nazwa każdej funkcji zaczyna się od pierwszego znaku w linii i że słowo functionpojawia się również w tej linii. Każde inne użycie tego słowa functionpowinno pojawić się w wierszu rozpoczynającym się spacją lub tabulatorem. Zatem jedno-liniowe rozwiązanie to:

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

Zalety tego podejścia są takie

  • to jest bardzo proste . Reguły są po prostu określone i jest tylko jedna prosta linia kodu R potrzebna do wyodrębnienia nazw funkcji. Regex jest również prosty, a dla istniejącego pliku bardzo łatwo go sprawdzić - po prostu grep słowo functioni sprawdź, czy każde wyświetlane wystąpienie jest zgodne z regułą.

  • nie ma potrzeby uruchamiania źródła. Jest całkowicie statyczny .

  • w wielu przypadkach nie trzeba wcale zmieniać pliku źródłowego, a w innych będą minimalne zmiany. Jeśli piszesz skrypt od nowa, mając na uwadze to, łatwiej jest go zorganizować.

Istnieje wiele innych alternatyw związanych z ideą konwencji. możesz mieć bardziej wyrafinowane wyrażenie regularne lub możesz dodać# FUNCTION na końcu pierwszego wiersza dowolnej definicji funkcji, jeśli piszesz skrypt od zera, a następnie wyszukujesz tę frazę i wyodrębniasz pierwsze słowo z wiersza, ale główna sugestia wydaje się tutaj szczególnie atrakcyjny ze względu na swoją prostotę i inne wymienione zalety.

Test

# 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
źródło
lapply(x, function(y) dostuff(y))
zepsułbym
@alan ocallaghan, Twój przykład narusza określone zasady, więc nie mógł zostać poprawnie wydany. Aby to napisać i nadal przestrzegać zasad, trzeba by było uruchomić funkcję na nowej linii, która jest wcięta, lub trzeba by było wciąć wcięcie.
G. Grothendieck
Sądzę, że narzędzie to ulega znacznej degradacji, jeśli potrzebujesz określonego formatowania, ponieważ może to wymagać zmiany pliku - w takim przypadku możesz również zasugerować użytkownikowi ręczne odczytanie nazw funkcji
alan ocallaghan
1
Jest to tylko kwestia do rozważenia, jeśli nie kontrolujesz pliku, ale wykluczyliśmy tę możliwość. Używanie konwencji jest bardzo powszechne w programowaniu. Często umieszczam w # TODOcałym kodzie, aby na przykład grep mój do zrobienia. Inną możliwością w tym samym # FUNCTIONwierszu byłoby zapisanie na końcu pierwszego wiersza dowolnej definicji funkcji.
G. Grothendieck
1
próba parsowania z wyrażeniem regularnym to droga do piekła ....
TMS
0

To dostosowuje kod użyty w poście z mojego komentarza, aby wyszukać sekwencję tokenów (symbol, operator przypisania, a następnie funkcja) i powinien pobrać dowolne zdefiniowane funkcje. Nie jestem pewien, czy jest solidny jako odpowiedź MrFlick, ale jest to inna opcja:

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"]]
}
Andrzej
źródło