Отримати всі джерела функцій


11

У R я використовую source()для завантаження деяких функцій:

source("functions.R")

Чи можливо отримати список усіх функцій, визначених у цьому файлі? Як імена функцій. (Може, source()сама може якось це повернути?).

PS: Останнім засобом було б зателефонувати source()вдруге на кшталт, local({ source(); })а потім виконувати функції ls()всередині та фільтрувати, але це занадто складно - чи є простіше та менш незграбне рішення?


1
Це не використовується source(), але ця стара тема може зацікавити вас.
Андрій

1
@Andrew спасибі, я перевірив запропоновані рішення, але це звучить набагато божевільніше, ніж останній спосіб, який я представив у запитанні :)
TMS

2
Я не знаю, це рішення є божевільнішим:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris

2
Складіть пакет із вихідних файлів. Тоді ви отримуєте всі переваги, включаючи простір імен пакетів.
Roland

@TMS, неправильно зрозумів ваше запитання / не прочитав, що ви хочете визначити функції. Вибачте!
Андрій

Відповіді:


7

Я думаю, що найкращим способом було б передати файл у тимчасове середовище. Перевірте це середовище для всіх функцій, а потім скопіюйте ці значення в батьківське середовище.

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

дякую, це рішення виглядає багатообіцяючим, як єдине зараз! Напрочуд, той, що має найменші результати. Це я, про яку я згадував, як крайній засіб, але використовуючи new.env()замість елегантного, local({ })який я не впевнений, чи буде він працювати з assignбатьківським кадром.
TMS

1) ви думаєте, що це спрацювало б local()? І BTW, 2) що ви робите в циклі for: чи не існує якоїсь функції для об'єднання середовищ?
TMS

1
@TMS Це може працювати з локальними, хоча я не пробував цього. Я не знаю іншого способу копіювання всіх змінних з одного середовища в інше. Це не звичайна операція.
MrFlick

Я думаю, що attachможна звикнути, щоб приєднати одне середовище до іншого. Хоча вам доведеться використовувати posаргумент, а не вказувати parent.frame. І це буде добре працювати лише для копіювання всього середовища, forцикл MrFlick дозволяє копіювати лише ті функції.
Грегор Томас

5

Це трохи незграбно, але ви можете подивитися на зміни в об'єктах до і після sourceдзвінка, як це.

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

Дякую! У мене була така ідея, але вона не працює з дуже простої причини - якщо пакет уже був завантажений (що трапляється весь час, коли я налагоджую код, я просто перезавантажую джерела), то він нічого не повертає.
TMS

3

Я думаю, що цей регулярний вираз охоплює майже кожен дійсний тип функції (двійковий оператор, функції присвоєння) та кожен дійсний символ у імені функції, але, можливо, я пропустив кращий регістр.

# 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 Я думаю, що це насправді не вдале рішення, але це, безумовно, задоволення . Я, мабуть, перетворив би файл в пакет, якщо мені справді потрібна ця інформація.
alan ocallaghan

Я пропустив два краєвидні випадки! Функції можуть починатися з .функцій присвоєння ( `foo<-`<- function(x, value)існують.
alan ocallaghan

Я використовую =для призначення, це не зачепить жодну з моїх функцій ...
Грегор Томас

Хороший улов - відредагований. Зауважу, що R дозволяє вам робити дурні речі, такі, ` d d` <- function(x)які наразі не спіймані. Я не хочу, щоб регулярний вираз ставав занадто нерозумним, хоча я можу переглянути його.
alan ocallaghan

Крім того , можна призначити функції з assign, <<-і ->. І буде дуже важко зробити такий підхід для врахування функцій, визначених у межах функцій, але насправді вони не знаходяться в середовищі, що отримується. Ваша відповідь повинна працювати дуже добре для стандартних випадків, але ви насправді не хочете писати R аналізатор із регулярного вираження.
Грегор Томас

1

Якщо це ваш власний сценарій, щоб у вас був контроль над тим, як він відформатований, буде достатньо простої конвенції. Просто переконайтеся, що ім’я кожної функції починається з першого символу на його рядку, і що слово functionтакож з’являється в цьому рядку. Будь-яке інше вживання слова functionповинно з’являтися на рядку, який починається з пробілу чи вкладки. Тоді однолінійне рішення:

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

Переваги такого підходу полягають у тому, що

  • це дуже просто . Правила викладені просто, і для вилучення імен функцій потрібен лише один простий рядок коду R. Regex також простий, і для вже наявного файлу його дуже легко перевірити - просто приберіть слово functionі перевірте, чи відповідає кожне відображене виникнення за правилом.

  • не потрібно запускати джерело. Це повністю статично .

  • у багатьох випадках вам взагалі не потрібно буде змінювати вихідний файл, а в інших - мінімальні зміни. Якщо ви пишете сценарій з нуля, маючи це на увазі, це ще простіше домовитись.

Існує багато інших альтернатив уздовж ідеї конвенцій. у вас може бути більш складний регулярний вираз або ви можете додати # FUNCTIONв кінці першого рядка будь-якого визначення функції, якщо ви пишете сценарій з нуля, а потім виграбуєте цю фразу і витягуєте перше слово в рядку, але головна пропозиція тут здається особливо привабливий через свою простоту та інші перелічені переваги.

Тест

# 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))порушив би це
alan ocallaghan

@alan ocallaghan, Ваш приклад порушує вказані правила, тому він не може дійсно статися. Щоб писати це і залишатися за правилами, потрібно було б запустити функцію на новому рядку, який має відступ, або можна було б відступити відступ.
Г. Гротендієк

Я думаю, що утиліта сильно деградує, якщо вам потрібне певне форматування, оскільки це може зажадати зміни файлу - у такому випадку ви можете також запропонувати користувачеві прочитати імена функцій вручну
alan ocallaghan

1
Це лише врахування, якщо ви не контролюєте файл, але ми виключили цю можливість. Використання конвенцій дуже часто зустрічається в програмуванні. Я часто # TODOввожу код свого коду, щоб я, наприклад, мав змогу виконувати свої завдання. Іншою можливістю вздовж тих самих рядків буде запис # FUNCTIONу кінці першого рядка будь-якого визначення функції.
Г. Гротендієк

1
спроба зробити розбір з регулярним виразом - це дорога до пекла ....
TMS

0

Це адаптує код, використаний у публікації з мого коментаря, для пошуку послідовності лексем (символ, оператор призначення, потім функція), і він повинен захоплювати будь-які визначені функції. Я не впевнений, чи надійний він як відповідь MrFlick, але це інший варіант:

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"]]
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.