Ottieni tutte le funzioni di provenienza


11

In R, sto usando source()per caricare alcune funzioni:

source("functions.R")

È possibile ottenere l'elenco di tutte le funzioni definite in questo file? Come nomi di funzioni. (Forse lo source()stesso può in qualche modo restituirlo?).

PS: L'ultima risorsa sarebbe quella di chiamare la source()seconda volta local({ source(); })e poi fare ls()funzioni interne e di filtro, ma è troppo complicato: esiste una soluzione più semplice e meno goffa?


1
Questo non usa source(), ma questo vecchio thread potrebbe essere di tuo interesse.
Andrew,

1
@Andrew grazie, ho controllato le soluzioni proposte ma sembra molto più folle dell'ultima risorsa che ho presentato nella domanda :)
TMS

2
Non lo so, questa soluzione è più envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
folle

2
Crea un pacchetto dai tuoi file sorgente. Quindi ottieni tutti i vantaggi incluso uno spazio dei nomi dei pacchetti.
Roland

@TMS, ho frainteso la tua domanda / non ho letto che volevi funzioni definite . Scuse!
Andrew,

Risposte:


7

Penso che il modo migliore sarebbe quello di trovare il file in un ambiente temporaneo. Interroga quell'ambiente per tutte le funzioni, quindi copia quei valori nell'ambiente padre.

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

grazie, questa soluzione sembra promettente, come l'unica in questo momento! Sorprendentemente, quello con meno voti. È quello che ho citato come ultima risorsa, ma usando al new.env()posto dell'elegante local({ })non sono sicuro che funzionerebbe con il assignframe principale.
TMS

1) pensi che funzionerebbe local()? E a proposito, 2) cosa fai nel ciclo for: non c'è qualche funzione per unire gli ambienti?
TMS

1
@TMS Potrebbe funzionare con local, anche se non l'ho provato. Non sono a conoscenza di un altro modo per copiare tutte le variabili da un ambiente all'altro. Non è un'operazione comune.
MrFlick

Penso che attachpossa essere usato per, bene, collegare un ambiente a un altro. Anche se devi usare l' posargomento piuttosto che specificare il parent.frame. E funzionerà bene solo per la copia dell'intero ambiente, il forciclo di MrFlick ti consente di copiare solo le funzioni.
Gregor Thomas,

5

È un po 'goffo ma potresti guardare i cambiamenti negli oggetti prima e dopo la sourcechiamata in questo modo.

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

Grazie! Ho avuto anche questa idea, ma non funziona per una ragione molto semplice: se il pacchetto era già stato caricato (cosa che accade continuamente quando eseguo il debug del codice, ho appena effettuato il rinvio delle fonti), quindi non restituisce nulla.
TMS

3

Penso che questo regex rilevi quasi ogni tipo valido di funzione (operatore binario, funzioni di assegnazione) e ogni carattere valido in un nome di funzione, ma potrei aver perso un caso limite.

# 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
A proposito, penso che questa non sia davvero una buona soluzione, ma è sicuramente una soluzione divertente . Vorrei probabilmente convertire il file in un pacchetto se davvero avessi bisogno di queste informazioni.
alan ocallaghan,

Ho perso due casi limite! Le funzioni possono iniziare con le .funzioni di assegnazione ( `foo<-`<- function(x, value)esistono.
alan ocallaghan,

Uso =per il compito, questo non catturerà nessuna delle mie funzioni ...
Gregor Thomas,

Buona cattura - modificata. Noterò che R ti consente di fare cose stupide come quelle ` d d` <- function(x)che al momento non vengono catturate. Non voglio che la regex diventi troppo sciocca, anche se potrei rivisitare.
alan ocallaghan,

Inoltre, è possibile assegnare funzioni con assign, <<-e ->. E sarà molto difficile rendere questo approccio responsabile delle funzioni definite all'interno delle funzioni, ma che in realtà non si trovano nell'ambiente di provenienza. La tua risposta dovrebbe funzionare molto bene per i casi standard, ma in realtà non vuoi scrivere un parser R su regex.
Gregor Thomas,

1

Se questo è il tuo script in modo da avere il controllo su come è formattato, sarebbe sufficiente una semplice convenzione. Assicurati solo che il nome di ogni funzione inizi dal primo carattere sulla sua riga e che la parola functionappaia anche su quella riga. Qualsiasi altro uso della parola functiondovrebbe apparire su una riga che inizia con uno spazio o una scheda. Quindi una soluzione a una riga è:

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

I vantaggi di questo approccio sono quelli

  • è molto semplice . Le regole sono semplicemente dichiarate e c'è solo una semplice riga di codice R necessaria per estrarre i nomi delle funzioni. Regex è anche semplice e per un file esistente è molto facile controllare - basta greppare la parola functione controllare se ogni occorrenza visualizzata segue la regola.

  • non è necessario eseguire la fonte. È completamente statico .

  • in molti casi non sarà necessario modificare affatto il file sorgente e in altri ci saranno cambiamenti minimi. Se stai scrivendo la sceneggiatura da zero con questo in mente, è ancora più facile da organizzare.

Ci sono molte altre alternative all'idea delle convenzioni. potresti avere una regex più sofisticata o potresti aggiungere # FUNCTIONalla fine della prima riga di qualsiasi definizione di funzione se stai scrivendo lo script da zero e quindi estrai quella frase ed estrai la prima parola sulla riga ma il suggerimento principale qui sembra particolarmente attraente per la sua semplicità e gli altri vantaggi elencati.

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))lo spezzerei
alan ocallaghan,

@alan ocallaghan, Il tuo esempio viola le regole dichiarate, quindi non può verificarsi validamente. Per scrivere questo e rimanere all'interno delle regole, bisognerebbe avviare la funzione su una nuova riga che è rientrata o che potrebbe essere necessario rientrare lappatura.
G. Grothendieck,

Penso che l'utilità sia notevolmente degradata se è richiesta una formattazione specifica, poiché ciò potrebbe richiedere la modifica del file - nel qual caso, potresti anche suggerire all'utente di leggere manualmente i nomi delle funzioni
alan ocallaghan,

1
Questa è solo una considerazione se non controlli il file ma abbiamo escluso questa possibilità. L'uso delle convenzioni è molto comune nella programmazione. Metto spesso # TODOtutto il mio codice in modo da poter fare il grep del mio fare, per esempio. Un'altra possibilità lungo le stesse righe sarebbe quella di scrivere # FUNCTIONalla fine della prima riga di qualsiasi definizione di funzione.
G. Grothendieck,

1
cercare di analizzare Regex è la strada per l'inferno ....
TMS,

0

Questo adatta il codice utilizzato nel post dal mio commento per cercare una sequenza di token (simbolo, operatore di assegnazione, quindi funzione) e dovrebbe prendere qualsiasi funzione definita. Non sono sicuro che sia affidabile come la risposta di MrFlick, ma è un'altra opzione:

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"]]
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.