【问题标题】:Get all sourced functions获取所有源函数
【发布时间】:2020-03-11 16:28:35
【问题描述】:

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

source("functions.R")

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

PS:最后的办法是第二次调用source(),就像local({ source(); }),然后在里面做ls()和过滤函数,但这太复杂了——有没有更简单、更简单的解决方案?

【问题讨论】:

  • 这里没有使用source(),但是你可能对这个old thread感兴趣。
  • @Andrew 谢谢,我已经检查了建议的解决方案,但这听起来比我在问题中提出的最后手段更疯狂:)
  • 我不知道这个解决方案更疯狂:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
  • 用你的源文件制作一个包。然后您将获得包括包命名空间在内的所有优势。
  • @TMS,误解了你的问题/没有读到你想要 defined 函数。道歉!

标签: r


【解决方案1】:

我认为最好的方法是将文件源到临时环境中。查询该环境的所有函数,然后将这些值复制到父环境。

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
  • 1) 你认为它适用于local() 吗?顺便说一句,2)你在 for 循环中做了什么:没有一些合并环境的功能吗?
  • @TMS 它可能适用于本地,但我没有尝试过。我不知道将所有变量从一个环境复制到另一个环境的另一种方法。这不是常见的操作。
  • 我认为attach 可以用来将一个环境附加到另一个环境。尽管您必须使用pos 参数而不是指定parent.frame。而且它只适用于复制整个环境,MrFlick 的for 循环让您只复制功能。
【解决方案2】:

这有点笨拙,但您可以像这样查看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"

【讨论】:

  • 谢谢!我也有这个想法,但由于非常简单的原因它不起作用 - 如果包已经加载(当我调试代码时一直发生这种情况,我只是重新获取源代码),那么它什么也不返回。跨度>
【解决方案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<-`"

【讨论】:

  • fyi 我认为这不是一个真正的解决方案,但它绝对是一个有趣的解决方案。如果我真的需要这些信息,我可能会将文件转换为一个包。
  • 我错过了两个边缘案例!函数可以以. 开头,赋值函数(`foo&lt;-`&lt;- function(x, value) 存在。
  • 我使用= 进行分配,这不会捕获我的任何功能...
  • 好消息 - 已编辑。我会注意到 R 可以让你做一些愚蠢的事情,比如 ` d d` &lt;- function(x) 目前还没有被抓住。我不希望正则表达式变得太傻,尽管我可能会重新审视。
  • 另外,您可以使用assign&lt;&lt;--&gt; 分配函数。并且很难让这种方法考虑定义在 within 函数中但实际上不在源环境中的函数。您的答案应该适用于标准情况,但您实际上并不想用正则表达式编写 R 解析器。
【解决方案4】:

如果这是您自己的脚本,以便您可以控制它的格式,那么一个简单的约定就足够了。只需确保每个函数名称从其行的第一个字符开始,并且单词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)) 会打破这个
  • @alan ocallaghan,您的示例违反了规定的规则,因此无法有效发生。要编写此代码并仍然遵守规则,必须在缩进的新行上开始函数,或者必须缩进 lapply。
  • 我认为如果您需要特定格式,该实用程序会大大降低,因为这可能需要更改文件 - 在这种情况下,您不妨建议用户手动读取函数名称
  • 如果您不控制文件,这只是一个考虑因素,但我们已排除这种可能性。使用约定在编程中很常见。例如,我经常将# TODO 放在我的代码中,这样我就可以 grep 我的待办事项。另一种可能的做法是在任何函数定义的第一行末尾写上# FUNCTION
  • 另一种可能性是手动复制所有函数名称并将它们粘贴到列表中,这需要与后一个建议大致相同的工作量,但并不真正符合问题。
【解决方案5】:

这调整了我评论中帖子中使用的代码,以搜索一系列标记(符号、赋值运算符,然后是函数),并且它应该获取任何已定义的函数。我不确定它是否像 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"]]
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-18
    • 1970-01-01
    • 1970-01-01
    • 2013-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-27
    相关资源
    最近更新 更多