Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

retrieve original version of package function even if over-assigned

Tags:

r

Suppose I replace a function of a package, for example knitr:::sub_ext. (Note: I'm particularly interested where it is an internal function, i.e. only accessible by ::: as opposed to ::, but the same answer may work for both).

library(knitr)
my.sub_ext <- function (x, ext) {
    return("I'm in your package stealing your functions D:")
}
# replace knitr:::sub_ext with my.sub_ext
knitr <- asNamespace('knitr')
unlockBinding('sub_ext', knitr)
assign('sub_ext', my.sub_ext, knitr)
lockBinding('sub_ext', knitr)

Question: is there any way to retrieve the original knitr:::sub_ext after I've done this? Preferably without reloading the package?


(I know some people want to know why I would want to do this so here it is. Not required reading for the question). I've been patching some functions in packages like so (not actually the sub_ext function...):

original.sub_ext <- knitr:::sub_ext
new.sub_ext <- function (x, ext) {
    # some extra code that does something first, e.g.
    x <- do.something.with(x)
    # now call the original knitr:::sub_ext
    original.sub_ext(x, ext)
}
# now set knitr:::sub_ext to new.sub_ext like before.

I agree this is not in general a good idea (in most cases these are quick fixes until changes make their way into CRAN, or they are "feature requests" that would never be approved because they are somewhat case-specific).

The problem with the above is if I accidentally execute it twice (e.g. it's at the top of a script that I run twice without restarting R in between), on the second time original.sub_ext is actually the previous new.sub_ext as opposed to the real knitr:::sub_ext, so I get infinite recursion.

Since sub_ext is an internal function (I wouldn't call it directly, but functions from knitr like knit all call it internally), I can't hope to modify all the functions that call sub_ext to call new.sub_ext manually, hence the approach of replacing the definition in the package namespace.

like image 986
mathematical.coffee Avatar asked May 10 '13 01:05

mathematical.coffee


1 Answers

When you do assign('sub_ext', my.sub_ext, knitr), you are irrevocably overwriting the value previously associated with sub_ext with the value of my.sub_ext. If you first stash the original value, though, it's not hard to reset it when you're done:

library(knitr)
knitr <- asNamespace("knitr")

## Store the original value of sub_ext
.sub_ext <- get("sub_ext", envir = knitr)

## Overwrite it with your own function
my.sub_ext <- function (x, ext) "I'm in your package stealing your functions D:"
assignInNamespace('sub_ext', my.sub_ext, knitr)
knitr:::sub_ext("eg.csv", "pdf")
# [1] "I'm in your package stealing your functions D:"

## Reset when you're done
assignInNamespace('sub_ext', .sub_ext, knitr)
knitr:::sub_ext("eg.csv", "pdf")
# [1] "eg.pdf"   

Alternatively, as long as you are just adding lines of code to what's already there, you could add that code using trace(). What's nice about trace() is that, when you are done, you can use untrace() to revert the function's body to its original form:

trace(what = "mean.default", 
      tracer = quote({
          a <- 1
          b <- 2
          x <- x*(a+b)
      }), 
      at = 1)
mean(1:2)
# Tracing mean.default(1:2) step 1 
# [1] 4.5
untrace("mean.default")
# Untracing function "mean.default" in package "base"
mean(1:2)
# [1] 1.5

Note that if the function you are tracing is in a namespace, you'll want to use trace()'s where argument, passing it the name of some other (exported) function that shares the to-be-traced function's namespace. So, to trace an unexported function in knitr's namespace, you could set where=knit

like image 123
Josh O'Brien Avatar answered Sep 29 '22 13:09

Josh O'Brien