【问题标题】:Identifying dependencies of R functions and scripts识别 R 函数和脚本的依赖关系
【发布时间】:2012-02-04 10:04:39
【问题描述】:

我正在筛选一个包和使用该包的脚本,并希望确定外部依赖项。目的是修改脚本指定library(pkgName),修改包中的函数使用require(pkgName),这样以后这些依赖会更加明显。

我正在修改代码以考虑每个外部依赖包。举个例子,虽然它绝不是确定的,但我现在发现很难识别依赖于data.table 的代码。我可以将data.table 替换为Matrixggplot2bigmemoryplyr 或许多其他包,因此请随时根据其他包的示例进行回答。

这个搜索并不容易。到目前为止我尝试过的方法包括:

  • 在代码中搜索libraryrequire 语句
  • 搜索提及data.table(例如library(data.table)
  • 尝试运行codetools::checkUsage 以确定可能存在问题的位置。对于脚本,我的程序将脚本插入到本地函数中,并将checkUsage 应用于该函数。否则,我使用 checkUsagePackage 作为包。
  • 寻找data.table 独有的语句,例如:=
  • 查找可以通过匈牙利表示法识别对象类的位置,例如DT

我搜索的本质是找到:

  • 正在加载data.table
  • 名称表明它们是data.table 对象的对象,
  • 似乎是data.table-specific 的方法

其中唯一简单的部分似乎是找到包的加载位置。不幸的是,并非所有功能都可以显式加载或需要外部包 - 这些可能假定它已经被加载。这是一个不好的做法,我正在尝试解决它。但是,搜索对象和方法似乎具有挑战性。

这个 (data.table) 只是一个包,一个似乎是有限且有些独特的用法。假设我想寻找 ggplot 函数的用途,其中选项更广泛,并且语法文本不那么特殊(即,+ 的频繁使用不是特殊的,而 := 似乎是)。

我认为静态分析不会给出完美的答案,例如可以将参数传递给函数,该函数指定要加载的包。尽管如此:是否有任何核心工具或软件包可以通过静态或动态分析来改进这种蛮力方法?

对于它的价值,tools::pkgDepends 仅解决包级别的依赖关系,而不是函数或脚本级别,这是我正在工作的级别。


更新 1:应该工作的动态分析工具的一个示例是报告在代码执行期间加载了哪些包。不过,我不知道 R 中是否存在这样的功能 - 就像 Rprof 报告 search() 的输出而不是代码堆栈。

【问题讨论】:

  • 你能试试foodwebmvbutils 吗?我自己没有这方面的经验,但对我来说似乎很有希望(除了我不知道它搜索的深度)。 foodweb(where='package:data.table',prune='function_youre_examining') 之类的东西?
  • @mathematical.coffee 这很有趣。看起来它在包内非常有用;我还不清楚它可以做什么跨包,但我会试一试,谢谢!
  • @mathematical.coffee 这很有趣。我正在进一步解决它。您可以发表您的评论作为答案吗?假设我可以让它工作,我可能会帮助将它编辑成一个解决方案。我之前评论说它似乎不适用于跨包,但这是不正确的。 where 参数似乎可以用来管理搜索空间。顺便说一句,changed.funs 的帮助读起来几乎就像我写我最近的一些经历一样。 :)
  • 动态地做很容易。使用trace修改一个包中的所有函数,记录函数名和调用栈。
  • 随意回答你自己的问题并接受它——我知道foodweb,但我自己没有使用过,所以你是完成所有艰苦工作的人! :)

标签: r dependencies code-analysis


【解决方案1】:

首先,感谢 @mathematical.coffee 让我走上了使用 Mark Bravington 的 mvbutils 软件包的道路。 foodweb 功能非常令人满意。

回顾一下,我想知道如何检查一个包,比如myPackage 和另一个,比如externalPackage,以及检查脚本和externalPackage。我将演示如何做每一个。在这种情况下,外部包是data.table

1:对于myPackagedata.table,以下命令就足够了:

library(mvbutils)
library(myPackage)
library(data.table)
ixWhere <- match(c("myPackage","data.table"), search())
foodweb(where = ixWhere, prune = ls("package:data.table"), descendents = FALSE)

这会生成一个出色的图表,显示哪些函数依赖于data.table 中的函数。尽管该图包含了data.table 内的依赖关系,但它并不过分繁琐:我可以很容易地看到我的哪些函数依赖于data.table,以及它们使用了哪些函数,例如as.data.tabledata.table:=、@ 987654339@,等等。在这一点上,可以说包依赖问题已经解决了,但是foodweb 提供了更多,所以让我们来看看。最酷的部分是依赖矩阵。

depMat  <- foodweb(where = ixWhere, prune = ls("package:data.table"), descendents = FALSE, plotting = FALSE)
ix_sel  <- grep("^myPackage.",rownames(depMat))
depMat  <- depMat[ix_sel,]
depMat  <- depMat[,-ix_sel]
ix_drop <- which(colSums(depMat) == 0)
depMat  <- depMat[,-ix_drop]
ix_drop <- which(rowSums(depMat) == 0)
depMat  <- depMat[-ix_drop,]

这很酷:它现在显示了我的包中函数的依赖关系,我在其中使用了详细的名称,例如myPackage.cleanData,关于功能不 在我的包中,即data.table 中的函数,它消除了没有依赖关系的行和列。这很简洁,让我可以快速调查依赖关系,并且通过处理rownames(depMat),我也可以很容易地找到我的函数的补充集。

注意:plotting = FALSE 似乎并没有阻止创建绘图设备,至少在调用序列中第一次调用 foodweb 时是这样。这很烦人,但并不可怕。也许我做错了什么。

2:对于脚本与data.table,这变得更有趣了。对于每个脚本,我需要创建一个临时函数,然后检查依赖项。我在下面有一个小功能,正是这样做的。

listFiles <- dir(pattern = "myScript*.r")
checkScriptDependencies <- function(fname){
    require(mvbutils)
    rawCode  <- readLines(fname)
    toParse  <- paste("localFunc <- function(){", paste(rawCode, sep = "\n", collapse = "\n"), "}", sep = "\n", collapse = "")
    newFunc  <- eval(parse(text = toParse))
    ix       <- match("data.table",search())
    vecPrune <- c("localFunc", ls("package:data.table"))
    tmpRes   <- foodweb(where = c(environment(),ix), prune = vecPrune, plotting = FALSE)
    tmpMat   <- tmpRes$funmat
    tmpVec   <- tmpMat["localFunc",]
    return(tmpVec)
}

listDeps <- list()
for(selFile in listFiles){
    listDeps[[selFile]] <- checkScriptDependencies(selFile)
}

现在,我只需要看看listDeps,我就有了与上面的 depMat 相同的奇妙的小见解。我从我编写的其他代码修改了checkScriptDependencies,这些代码发送脚本供codetools::checkUsage分析;有一个像这样的小函数来分析独立代码是很好的。感谢@Spacedman@Tommy 提供的见解改进了对foodweb 的调用,使用environment()

(真正的匈牙利人会注意到我与名称和类型的顺序不一致 - 太糟糕了。:) 这有一个更长的原因,但这并不是我正在使用的代码。)


虽然我没有发布foodweb 为我的代码生成的图表的图片,但您可以在http://web.archive.org/web/20120413190726/http://www.sigmafield.org/2010/09/21/r-function-of-the-day-foodweb 看到一些很好的示例。在我的例子中,它的输出肯定会捕获 data.table 对:=J 的使用,以及标准命名函数,例如keyas.data.table。它似乎避免了我的文本搜索,并且在几个方面都有改进(例如,查找我忽略的功能)。

总而言之,foodweb 是一个出色的工具,我鼓励其他人探索 mvbutils 包和 Mark Bravington 的一些其他不错的包,例如 debug。如果您确实安装了mvbutils,如果您认为只有您在管理不断发展的 R 代码方面遇到困难,请查看?changed.funs。 :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-23
    • 1970-01-01
    • 2016-04-11
    • 1970-01-01
    • 2012-12-21
    • 1970-01-01
    • 2012-03-03
    相关资源
    最近更新 更多