Obter todas as funções de origem


11

No R, estou usando source()para carregar algumas funções:

source("functions.R")

É possível obter a lista de todas as funções definidas neste arquivo? Como nomes de funções. (Talvez source()ele próprio possa devolvê-lo de alguma forma?).

PS: O último recurso seria chamar a source()segunda vez como local({ source(); })e depois ls()executar funções internas e de filtro, mas isso é muito complicado - existe solução mais fácil e menos desajeitada?


11
Isso não usa source(), mas esse segmento antigo pode ser do seu interesse.
18719 Andrew Andrew

11
@ Andrew graças, eu verifiquei as soluções propostas, mas que sons maneira mais louca do que o último recurso I apresentado na pergunta :)
TMS

2
Não sei se essa solução é mais louca:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris 15/11/19

2
Faça um pacote com seus arquivos de origem. Então você obtém todas as vantagens, incluindo um espaço para nome do pacote.
Roland

@TMS, não entendeu a sua pergunta / não leu que você queria funções definidas . Desculpas!
18719 Andrew Andrew

Respostas:


7

Eu acho que a melhor maneira seria originar o arquivo em um ambiente temporário. Consulte esse ambiente para todas as funções e copie esses valores para o ambiente pai.

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

obrigado, esta solução parece promissora, como a única agora! Surpreendentemente, aquele com menos votos positivos. É o que eu mencionei como último recurso, mas usando em new.env()vez do elegante local({ })que não tenho certeza se funcionaria com o assignquadro pai.
TMS

1) você acha que funcionaria local()? E BTW, 2) o que você faz no loop for: não há alguma função para mesclar ambientes?
TMS

11
@TMS Pode funcionar com local, embora eu não tenha tentado. Não conheço outra maneira de copiar todas as variáveis ​​de um ambiente para outro. Não é uma operação comum.
MrFlick

Eu acho que attachpode ser usado para, bem, conectar um ambiente a outro. Embora você precise usar o posargumento em vez de especificar o parent.frame. E isso só funcionará bem para copiar todo o ambiente, o forloop do MrFlick permite copiar apenas as funções.
Gregor Thomas

5

É um pouco desajeitado, mas você pode observar alterações nos objetos antes e depois da sourcechamada dessa maneira.

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

Obrigado! Eu também tive essa ideia, mas ela não funciona por uma razão muito simples - se o pacote já estiver carregado (o que acontece o tempo todo quando depuro o código, eu apenas recoloquei as fontes), ele não retorna nada.
TMS

3

Eu acho que esse regex captura quase todos os tipos de funções válidas (operador binário, funções de atribuição) e todos os caracteres válidos em um nome de função, mas eu posso ter perdido um caso extremo.

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

11
Eu acho que essa não é realmente uma boa solução, mas é definitivamente uma solução divertida . Provavelmente converteria o arquivo em um pacote se realmente precisasse dessas informações.
alan Ocallaghan

Perdi dois casos extremos! As funções podem começar com .e funções de atribuição ( `foo<-`<- function(x, value)exist.
alan Ocallaghan

Eu uso =para atribuição, isso não vai pegar nenhuma das minhas funções ...
Gregor Thomas

Boa captura - editada. Observarei que R permite que você faça coisas tolas como as ` d d` <- function(x)que atualmente não são capturadas. Eu não quero que o regex fique muito bobo, embora eu possa revisitar.
alan Ocallaghan

Além disso, você pode atribuir funções com assign, <<-e ->. E será muito difícil tornar essa abordagem responsável pelas funções definidas dentro das funções, mas que não estão realmente no ambiente de origem. Sua resposta deve funcionar muito bem para casos padrão, mas você realmente não deseja escrever um analisador R a partir de regex.
Gregor Thomas

1

Se esse for seu próprio script, para que você tenha controle sobre como ele é formatado, uma simples convenção seria suficiente. Apenas verifique se o nome de cada função começa no primeiro caractere de sua linha e se a palavra functiontambém aparece nessa linha. Qualquer outro uso da palavra functiondeve aparecer em uma linha que começa com um espaço ou tabulação. Em seguida, uma solução de uma linha é:

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

As vantagens dessa abordagem são que

  • é muito simples . As regras são simplesmente declaradas e há apenas uma linha simples de código R necessária para extrair os nomes das funções. O Regex também é simples e, para um arquivo existente, é muito fácil verificar - apenas cumprimente a palavra functione verifique se cada ocorrência exibida segue a regra.

  • não há necessidade de executar a fonte. É totalmente estático .

  • em muitos casos, você não precisará alterar o arquivo de origem e, em outros, haverá alterações mínimas. Se você estiver escrevendo o script do zero, com isso em mente, é ainda mais fácil organizar.

Existem muitas outras alternativas ao longo da ideia de convenções. você pode ter um regex mais sofisticado ou adicionar # FUNCTIONno final da primeira linha de qualquer definição de função se estiver escrevendo o script do zero e depois cumprimentar essa frase e extrair a primeira palavra da linha, mas a principal sugestão aqui parece particularmente atraente devido à sua simplicidade e às outras vantagens listadas.

Teste

# 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))iria quebrar isso
alan ocallaghan 15/11/19

@alan ocallaghan, Seu exemplo viola as regras declaradas e, portanto, não pode ocorrer validamente. Para escrever isso e ainda permanecer dentro das regras, seria necessário iniciar a função em uma nova linha recuada ou poderia ser necessário recuar a lapply.
G. Grothendieck

Eu acho que o utilitário é maciçamente degradado se você precisar de formatação específica, uma vez que pode exigir alterar o arquivo - e nesse caso, você pode também sugere que o usuário leia os nomes das funções manualmente
alan Ocallaghan

11
Isso é apenas uma consideração se você não controla o arquivo, mas excluímos essa possibilidade. O uso de convenções é muito comum na programação. Costumo colocar # TODOtodo o meu código para que eu possa cumprimentar minhas tarefas, por exemplo. Outra possibilidade ao longo das mesmas linhas seria escrever # FUNCTIONno final da primeira linha de qualquer definição de função.
G. Grothendieck

11
tentando fazer a análise com regex é a estrada para o inferno ....
TMS

0

Isso adapta o código usado na postagem do meu comentário para procurar uma sequência de tokens (símbolo, operador de atribuição e depois função), e deve pegar todas as funções definidas. Não tenho certeza se é robusto como resposta do MrFlick, mas é outra opção:

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"]]
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.