问题 包函数内的重定向/截取函数调用


假设我正在调用存在于第三方包(即来自CRAN的库)中的函数PackageFuncA。 PackageFuncA依次在同一个第三方包中调用PackageFuncB。有没有办法调用PackageFuncA,这样当它调用PackageFuncB时,它实际上会调用我自己的PackageFuncB实现?换句话说,我可以拦截对PackageFuncB的调用吗?

我认为解决方案涉及创建我自己的PackageFuncB函数,然后在同一环境中调用PackageFuncA而不是PackageFuncA的环境,但我无法使用do.call或eval。


8711
2017-11-20 19:20


起源

是否更容易创建自己的PackageFunA并更改对PackageFunB的调用,以便它调用您的函数? - joran
看到 ?assignInNamespace - Andrie
joran - 我宁愿不维护我自己的PackageFuncA版本,特别是因为它不仅仅是几行代码。 - SFun28
Andrie - 我实际上希望我的PackageFuncB版本成为第三方PackageFuncB的包装器。所以我的func首先被调用,我做了一些工作,然后传递给真正的PackageFuncB。所以我不想替换现有的功能。 - SFun28
我也不想在我的代码的其他部分影响PackageFuncB的功能,只是在一个我要重定向调用的实例中 - SFun28


答案:


这是一个单行班。这里 PackageFuncA 是 stats::acf 和 PackageFuncB 是 stats:::plot.acf 我们要替换的 my.plot.acf 。 my.plot.acf 版画 "Hello" 然后调用真实的 stats:::plot.acf 。

# we want this to run in place of stats:::plot.acf
my.plot.acf <- function(x, ...) { cat("Hello\n"); stats:::plot.acf(x, ...) }

# this does it
library(proto)
acf <- with(proto(environment(acf), acf = stats::acf, plot.acf = my.plot.acf), acf)

# test
acf(1:10)

proto对象是一个环境,任何函数都可以通过它插入到对象中 proto function将其环境自动重置为该对象。第一个arg proto() 是proto对象的父级。

在上面的例子中,它已被设置为 acf 变量是指版本的 acf 插入proto对象(与原始对象相同,除了其环境已被修改为proto对象)。当新的 acf 功能运行 plot.acf 是一个自由变量(即未定义) acf所以它被抬起来了 acf的父级,这是原型对象中找到新的环境 plot.acfacf 可能有其他自由变量,但在那些情况下,因为它们在proto对象中找不到,它会查看proto对象的父对象,它是原始对象的原始环境 acf。在图表方面,我们有这个 <- 表示左侧是右侧的父母:

environment(stats::acf) <- proto object <- revised acf

并且proto对象包含两个 plot.acf 和修改后的 acf 。

我们还设定了新环境 plot.acf 到proto对象。我们可能需要也可能不需要这样做。在许多情况下,这无关紧要。如果重要的是不设置新的环境 plot.acf 然后就会这样做因为proto永远不会设置插入函数的环境 [[...]] :

acf <- with(p <- proto(environment(acf), acf = stats::acf), acf)
p[["plot.acf"]] <- my.plot.acf

在这个例子中,两种方法都有效。

可以使用普通环境完成所有这些操作,但必须使用多行代码:

# create new environment whose parent is the original acf's parent
e <- new.env(parent = environment(stats::acf))

# the next statement is only need to overwrite any acf you already might have from
# trying other code.  If you were sure there was no revised acf already defined 
# then the next line could be omitted.  Its a bit safer to include it.
acf <- stats::acf

# This sets the environment of the new acf.  If there were no acf already here 
# then it would copy it from stats::acf .
environment(acf) <- e

# may or may not need next statement.  In this case it doesn't matter.
environment(my.plot.acf) <- e

e$plot.acf <- my.plot.acf

acf(1:10)

在这种情况下,我们没有放置修订版 acf 在 e 在原型示例中,但只设置其父级。事实上,放置修改 acf 成 e 或者proto对象不是绝对必要的,但只是在proto案例中完成,因为proto具有重置环境的副作用,而这正是我们追求的副作用。另一方面,有必要修改 plot.acf 在 e 或原始对象,以便在原始对象之前遇到它。

你可能想读这个  并且,特别是关于代理开始第21页的部分,因为此处显示的技术是代理对象的示例。


10
2017-11-20 21:47



效果很好!在弄清楚原因之前,我必须先盯着这一点。如果你有时间的话,简要介绍一下发生的事情将是一个巨大的帮助。 - SFun28
好。增加了一些细节。 - G. Grothendieck
这是太棒了!非常感谢 - SFun28


制作新的副本 PackageFuncA,重置其环境,并编写自己的PackageFuncB版本。

environment(PackageFuncA) <- globalenv()  # makes a new copy of PackageFuncA

PackageFuncB <- function(...) ....   # will be called from your new PackageFuncA

你可能需要做一些编辑 PackageFuncA 使用原始包中的未导出函数。此外,如果你不想要新的 PackageFuncB 要在其他地方使用,你可以将它包装在新的 PackageFuncA 而不是将其置于全球环境中。


0
2017-11-21 00:20