how to attach a package including the non-exported functions?
Asked Answered
L

3

5

When debugging functions from an external package, I often find myself copying the function to a new script to add my corrections, but I have to add a bunch of foo=extpack:::foo at the beginning of the script for the function to access the internal function of the package.

Cloning and building the package would be a total overkill in those situations when often a single line is changed.

Is there a way to attach the package with all its internal functions?

Something like library(extpack, attach_nonexported=TRUE)

Lordan answered 3/10, 2020 at 9:53 Comment(0)
D
1

The best way to do bug fixes like that is to set the environment of your modified function to the same environment as the original. For example, to fix extpack::badfn use

badfn <- function(...) { ... } # new version in your global workspace

environment(badfn) <- environment(extpack::badfn)

This means it will see the private functions when it makes calls, but it won't replace extpack::badfn in the original location, so other extpack functions that call it will still call the original one. If you want them to call yours instead, use

assignInNamespace("badfn", badfn, "extpack")

after making the change above.

If some other package imports extpack::badfn, what it gets will depend on the exact order of operations, so in a case like that you're best to bite the bullet and rebuild the whole package with your fixes in place.

Deucalion answered 3/10, 2020 at 13:40 Comment(1)
Allan's answer answers the question I asked, your answer answers the question I should have asked. I think you winLordan
I
11

You can get all functions from a package namespace as an environment by doing e.g.:

getNamespace("ggplot2")

So you can attach them to your search path (similar to calling library with unexported functions) by doing:

attach(getNamespace("ggplot2"))

If you prefer them in a list you can do

as.list(getNamespace("ggplot2"))

Or if you wish them to appear in the global workspace, you can do:

list2env(as.list(getNamespace("ggplot2")), globalenv())

Needless to say, you should only do this kind of thing with interactive sessions rather than when writing a package.

Intimidate answered 3/10, 2020 at 10:8 Comment(0)
P
3

if you really want to simulate a library call we can create a "package:yourpkg" on the searchpath containing all functions :

magrittr::as_pipe_fn
#> Error: 'as_pipe_fn' n'est pas un objet exporté depuis 'namespace:magrittr'
magrittr:::as_pipe_fn
#> function (expr, env) 
#> {
#>     eval(call("function", lambda_fmls, expr), env)
#> }
#> <bytecode: 0x0000000013917228>
#> <environment: namespace:magrittr>

attach_all <- function(pkg) {
  pkg <- as.character(substitute(pkg))
  pkg_long <- paste0("package:", pkg)
  eval(bquote(with(
    setNames(list(getNamespace(pkg)), pkg_long), 
    attach(.(as.symbol(pkg_long)))
  )))
}

attach_all(magrittr)

search()
#>  [1] ".GlobalEnv"        "package:magrittr"  "package:stats"    
#>  [4] "package:graphics"  "package:grDevices" "package:utils"    
#>  [7] "package:datasets"  "package:methods"   "Autoloads"        
#> [10] "tools:callr"       "package:base"
as_pipe_fn
#> function (expr, env) 
#> {
#>     eval(call("function", lambda_fmls, expr), env)
#> }
#> <bytecode: 0x0000000013917228>
#> <environment: namespace:magrittr>

Created on 2020-10-09 by the reprex package (v0.3.0)


It might make you nervous to use package:mypkg, feel free to change this part if so, the result will be the same.

Another way would be to attach the unexported functions to a different environment so you can detach it independently if needed :

attach_unexported <- function(pkg) {
  pkg <- as.character(substitute(pkg))
  all_funs <- lsf.str(asNamespace(pkg))
  exported <- getNamespaceExports(pkg)
  unexported_funs <- setdiff(all_funs, exported)
  pkg_long <- paste0(pkg, "_unexported")
  eval(bquote(with(
    setNames(list(mget(unexported_funs, asNamespace(pkg))), pkg_long), 
    attach(.(as.symbol(pkg_long)))
  )))
}

attach_unexported(magrittr)

search()
#>  [1] ".GlobalEnv"          "magrittr_unexported" "package:stats"      
#>  [4] "package:graphics"    "package:grDevices"   "package:utils"      
#>  [7] "package:datasets"    "package:methods"     "Autoloads"          
#> [10] "tools:callr"         "package:base"
as_pipe_fn
#> function (expr, env) 
#> {
#>     eval(call("function", lambda_fmls, expr), env)
#> }
#> <bytecode: 0x0000000013a05bd0>
#> <environment: namespace:magrittr>

`%>%`
#> Error in eval(expr, envir, enclos): objet '%>%' introuvable

Created on 2020-10-09 by the reprex package (v0.3.0)

Punctate answered 8/10, 2020 at 23:43 Comment(1)
The most interesting and valuable overkill ever!Lordan
D
1

The best way to do bug fixes like that is to set the environment of your modified function to the same environment as the original. For example, to fix extpack::badfn use

badfn <- function(...) { ... } # new version in your global workspace

environment(badfn) <- environment(extpack::badfn)

This means it will see the private functions when it makes calls, but it won't replace extpack::badfn in the original location, so other extpack functions that call it will still call the original one. If you want them to call yours instead, use

assignInNamespace("badfn", badfn, "extpack")

after making the change above.

If some other package imports extpack::badfn, what it gets will depend on the exact order of operations, so in a case like that you're best to bite the bullet and rebuild the whole package with your fixes in place.

Deucalion answered 3/10, 2020 at 13:40 Comment(1)
Allan's answer answers the question I asked, your answer answers the question I should have asked. I think you winLordan

© 2022 - 2024 — McMap. All rights reserved.