获取所有源函数


11

在R中,我source()用来加载一些函数:

source("functions.R")

是否可以获取此文件中定义的所有功能的列表?作为函数名称。(也许source()本身可以以某种方式返回它?)。

PS:最后的办法是source()第二次调用local({ source(); }),然后在ls()内部和过滤器函数中进行调用,但这太复杂了-有没有更简单,更简洁的解决方案?


1
它不使用source(),但是您可能会对这个旧线程感兴趣。
Andrew

1
@Andrew谢谢,我已经检查了所提出的解决方案,但是听起来比我在问题中提出的最后解决方法更疯狂:)
TMS

2
我不知道这个解决方案更疯狂:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris

2
用源文件制作一个包。然后,您将获得所有好处,包括包名称空间。
罗兰

@TMS,误解了您的问题/未读到您想要定义的函数。道歉!
Andrew

Answers:


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()吗?顺便说一句,2)您在for循环中所做的事情:没有用于合并环境的功能吗?
TMS

1
@TMS它可能与本地一起使用,尽管我没有尝试过。我不知道将所有变量从一个环境复制到另一个环境的另一种方法。这不是常见的操作。
MrFlick

我认为attach可以用来将一个环境附加到另一个环境。尽管您必须使用pos参数而不是指定parent.frame。而且它仅适用于复制整个环境,MrFlick的for循环可让您仅复制函数。
格雷戈尔·托马斯

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
仅供参考,我认为这并不是一个很好的解决方案,但绝对是一个有趣的解决方案。如果我确实需要此信息,我可能会将文件转换为包。
阿兰·ocallaghan

我错过了两个边缘案例!功能可以启动.和分配功能(`foo<-`<- function(x, value)存在。
艾伦ocallaghan

=用于分配任务,这不会捕获我的任何功能……
Gregor Thomas

不错-编辑。我会注意到R使您可以做一些愚蠢的事情,例如` d d` <- function(x)当前未被发现的事情。我不希望正则表达式变得太傻,尽管我可能会重新审视。
阿兰·ocallaghan

此外,您可以指定与功能assign<<-以及->。这将是很难使这种方法占被定义功能的功能,但实际上并不在采购环境。您的答案在标准情况下应该很好用,但是您实际上并不想用正则表达式编写R解析器。
格雷戈尔·托马斯

1

如果这是您自己的脚本,那么您可以控制其格式,那么简单的约定就足够了。只要确保每个函数名称都从其行的第一个字符开始,并且单词function也出现在该行。该单词的任何其他用法function都应出现在以空格或制表符开头的行上。那么一线解决方案是:

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

这种方法的优点是

  • 这很简单。规则很简单,提取功能名称只需要一行R代码。正则表达式也很简单,对于现有文件,它很容易检查-只需grep这个词function并检查所显示的每个匹配项是否符合规则。

  • 无需运行源代码。它是完全静态的

  • 在许多情况下,您根本不需要更改源文件,而在其他情况下,更改很少。如果您牢记这一点从头开始编写脚本,则安排起来会更加容易。

约定的概念还有许多其他选择。您可以使用更复杂的正则表达式,也可以添加# FUNCTION如果您是从头开始编写脚本,然后可以grep出该短语并提取该行的第一个单词,在任何函数定义的第一行末。由于其简单性和列出的其他优点而特别有吸引力。

测试

# 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))会打破这一点
阿兰·ocallaghan

@alan ocallaghan,您的示例违反了规定的规则,因此无法有效发生。要编写此代码并保持其在规则之内,则必须在缩进的新行上启动功能,或者可能必须缩进重叠的行。
G. Grothendieck,

我认为,如果您需要特定的格式,该实用程序将大大降低性能,因为这可能需要更改文件-在这种情况下,您还可能建议用户手动读取函数名称
alan ocallaghan

1
如果您不控制文件,这只是一个考虑因素,但是我们排除了这种可能性。在编程中使用约定非常普遍。例如,我经常放置# TODO我的所有代码,以便我可以重复做。沿同一行的另一种可能性将写# FUNCTION在任何函数定义的第一行的末尾。
G. Grothendieck,

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"]]
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.