【问题标题】:In R, how can I check for the existence of a function in an unloaded package?在 R 中,如何检查卸载包中是否存在函数?
【发布时间】:2015-10-18 21:16:16
【问题描述】:

我可以检查我的环境中是否存在一个函数:

> exists("is.zoo")
[1] FALSE

我可以通过加载一个包来检查一个函数是否存在:

> library(zoo)
> exists("is.zoo")
[1] TRUE

但是如何在不加载包的情况下检查包中是否存在函数

> exists("zoo::is.zoo")
[1] FALSE

【问题讨论】:

  • 我相信你需要查看包源。您通常可以在 NAMESPACE 文件中轻松找到导出的函数,但通常您也应该在包的文档中找到它们。否则你需要检查实际的源文件。
  • 我需要能够以编程方式进行检查。
  • 为什么需要以编程方式检查这个?
  • 您可以通过编写一个加载库、检查函数、然后卸载库并返回存在值的函数来作弊。
  • 目前不在一台装有 R 的机器上,因此无法测试,但也许使用 ::: 运算符搜索 NAMESPACE 可能涵盖大多数情况。

标签: r package exists


【解决方案1】:

这不是一个漂亮的答案,它可能有一些缺陷,但这是一个开始。

is_exported <- function(fn, pkg){
  nmsp <- readLines(system.file("NAMESPACE", package = pkg))
  nmsp <- paste0(nmsp, collapse = " ")

  Exports <- stringr::str_extract_all(nmsp,
                                   stringr::regex("(?<=export[(]).+?(?=[)])"))
  Methods <- stringr::str_extract_all(nmsp,
                                   stringr::regex("(?<=S3method[(]).+?(?=[)])"))

  any(grepl(stringr::regex(fn), c(Exports, Methods)))
}

is_exported("is.zoo", "zoo")

【讨论】:

  • 这大致就是devtools::parse_ns_file 所做的。问题是一些 NAMESPACE 文件使用exportPattern,因此没有按名称提及导出的对象......这意味着这种方法存在间歇性错误。
【解决方案2】:

即使未加载,您也可以查看函数的源代码。

> exists("zoo::is.zoo")
[1] FALSE
> zoo::is.zoo
function (object)
inherits(object, "zoo")
<environment: namespace:zoo>

所以你可以用这样的函数来利用它

exists_unloaded <- function(fn) {
  tryCatch( {
    fn
    TRUE
  }, error=function(e) FALSE
  )
}

如果对fn 的调用出错,它将返回FALSE;如果fn 显示源,则返回TRUE

> exists("zoo::is.zoo")
[1] FALSE
> exists_unloaded(zoo::is.zoo)
[1] TRUE
> exists_unloaded(zoo::is.zootoo)
[1] FALSE

(请注意,exists_unloaded 会为所有字符串返回TRUE。如果fn 是字符串,则可能会出错。)

编辑:

此外,您可以在不加载包的情况下调用函数。我不知道您的完整用例,但它可能不需要检查它的存在。 (当然,如果用户没有安装包,还是会失败。)

> exists("zoo::is.zoo")
[1] FALSE
> zoo::is.zoo(1)
> z <- zoo::as.zoo(1)
> zoo::is.zoo(z)
[1] TRUE

【讨论】:

  • parseNamespaceFile('zoo', .libPaths(),mustExist = TRUE) 可能在这里有用
  • 这个答案是错误的。如果您调用zoo::is.zoo(),它会加载包,正如您在isLoadedNamespace("zoo") 中看到的那样。它不会将包附加到搜索路径。但是您以后卸载它时可能会遇到问题 - 请参阅detach
【解决方案3】:

如果您不想使用 loadNamespace 使搜索路径混乱,可以与 getAnywhere 结合使用

请注意,这将找到未导出或已导出的函数...

loadNamespace('zoo')
x <- getAnywhere('is.zoo')
x[['where']]=='namespace:zoo'
# TRUE

将其包装在一个函数中

exist_pkg <- function(f, pkg){
  loadNamespace(pkg)
  x <- getAnywhere(f)
  paste0('namespace:',pkg) %in% x[['where']]
}

如果你真的想要的话,你可以小心卸载命名空间

你也可以使用getFromNamespace

is.function(getFromNamespace("is.zoo", "zoo"))
# TRUE

【讨论】:

    【解决方案4】:

    您可以使用exists 函数查看命名空间内部:

    exists2 <- function(x) {
    
        assertthat::assert_that(assertthat::is.string(x))
    
        split <- base::strsplit(x, "::")[[1]]
    
        if (length(split) == 1) {
            base::exists(split[1])
        } else if (length(split) == 2) {
            base::exists(split[2], envir = base::asNamespace(split[1]))
        } else {
            stop(paste0("exists2 cannot handle ", x))
        }
    }
    

    【讨论】:

      【解决方案5】:

      好的,这需要一个更准确的答案。

      短版:你不能。

      要了解原因,请考虑包中的以下代码:

      eval(parse(text = paste0("foo <- func", "tion () 1 + 1")))
      

      这将创建一个函数foo。但你只能通过运行 R 代码来了解这一点。

      您可以查看export(foo) 的NAMESPACE 文件,但不幸的是作者可能写了exportPattern("f.*") 之类的东西,所以这也不可靠。

      长版:你不能避免加载包,但你可以避免附加它。换句话说,R 将解释包源文件(并加载任何 dll),并将包存储在内存中,但它不会直接在搜索路径上可用。

      ns <- loadNamespace(package)
      exists("foo", ns)
      

      然后您可以使用unloadNamespace(package) 卸载命名空间。但是请参阅?detach 中的警告:这并不总能保证有效! loadNamespace(package, partial = TRUE) 可能会有所帮助,或者 devtools::load_alldevtools::unload 做一些更聪明的事情,我不知道。

      一些答案​​建议像try{package::foo} 这样的东西。问题是它本身会加载命名空间:

      > isNamespaceLoaded("broom")
      [1] FALSE
      > try(broom::tidy)
      function(x, ...) UseMethod("tidy")
      <environment: namespace:broom>
      > isNamespaceLoaded("broom")
      [1] TRUE
      

      【讨论】:

        猜你喜欢
        • 2020-06-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-05
        • 1970-01-01
        • 2015-11-06
        • 2014-02-18
        • 2015-07-24
        相关资源
        最近更新 更多