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?


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

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"

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<-`"

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"

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"]]
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.