简体   繁体   中英

In R, how can I check for the existence of a function in an unloaded package?

I can check that a function exists in my environment:

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

I can check that a function exists after loading a package by loading it:

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

But how can I check that a function exists in a package without loading that package?

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

You can view the source of a function even if its not loaded.

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

So you could exploit that with a function like this

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

If the call to fn errors, it'll return FALSE; if fn shows the source, the TRUE will be returned.

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

(Just be careful, as written exists_unloaded returns TRUE for all strings. Probably want to error if fn is a string.)

edit:

Also, you can call a function without loading the package. I don't know your full use case, but it might obviate the need to check for its existence. (Of course, if the user hasn't installed the package, it will still fail.)

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

If you didn't want to clutter the search path using loadNamespace would work in conjunction with getAnywhere

Note that this will find functions unexported or exported...

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

Wrap it in a function

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

you could be careful unloading namespaces afterwards if you really wanted

You could also use getFromNamespace

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

It isn't a pretty answer, and it probably has some flaws to it, but it's a start.

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

You can use the exists function to look inside namespaces:

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

OK, this needs a more accurate answer.

Short version: you can't.

To see why, consider the following code in a package:

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

This will create a function foo . But you will only learn that by running the R code.

You could check the NAMESPACE file for export(foo) , but unfortunately the author might have written something like exportPattern("f.*") , so that won't be reliable either.

Long version: you can't avoid loading the package, but you can avoid attaching it. In other words, R will interpret the package source files (and load any dlls), and will store the package in memory, but it won't be directly available on the search path.

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

You can then unload the namespace with unloadNamespace(package) . But see the warnings in ?detach : this is not always guaranteed to work! loadNamespace(package, partial = TRUE) may be helpful, or maybe devtools::load_all and devtools::unload do something cleverer, I don't know.

Some answers are suggesting stuff like try{package::foo} . The problem is that this itself loads the namespace:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM