有时,人们想要修补包中的某个函数,而不需要重新编译整个包。
例如,在 Emacs ESS 中,如果未加载 tcltk
,函数 install.packages()
可能会卡住。人们可能需要修补install.packages()
,以便在安装之前需要tcltk
,并在软件包安装后卸载它。
install.packages()
的 temp()
修补版本可能是:
## Get original args without ending NULL
temp=rev(rev(deparse(args(install.packages)))[-1])
temp=paste(paste(temp, collapse="\n"),
## Add code to load tcltk
"{",
" wasloaded= 'package:tcltk' %in% search()",
" require(tcltk)",
## Add orginal body without braces
paste(rev(rev(deparse(body(install.packages))[-1])[-1]), collapse="\n"),
## Unload tcltk if it was not loaded before by user
" if(!wasloaded) detach('package:tcltk', unload=TRUE)",
"}\n",
sep="\n")
## Eval patched function
temp=eval(parse(text=temp))
# temp
现在我们想要替换原来的 install.packages()
并可能将代码插入到 Rprofile
中。
为此,以下内容毫无值(value):
getAnywhere("install.packages")
# A single object matching 'install.packages' was found
# It was found in the following places
# package:utils
# namespace:utils
# with value
#
# ... install.packages() source follows (quite lengthy)
也就是说,该函数存储在utils
的包/命名空间内。该环境是密封的,因此在替换之前应解锁 install.packages()
:
## Override original function
unlockBinding("install.packages", as.environment("package:utils"))
assign("install.packages", temp, envir=as.environment("package:utils"))
unlockBinding("install.packages", asNamespace("utils"))
assign("install.packages", temp, envir=asNamespace("utils"))
rm(temp)
再次使用getAnywhere()
,我们得到:
getAnywhere("install.packages")
# A single object matching 'install.packages' was found
# It was found in the following places
# package:utils
# namespace:utils
# with value
#
# ... the *new* install.packages() source follows
看来修补的函数放置在正确的位置。
不幸的是,运行它会给出:
Error in install.packages(xxxxx) :
could not find function "getDependencies"
getDependency()
是同一 utils
包内的函数,但未导出;因此在其 namespace 之外无法访问它。
尽管有 getAnywhere("install.packages")
的输出,但修补后的 install.packages()
仍然错位。
问题是我们需要重新加载utils
库才能获得想要的效果,这也需要卸载导入它的其他库。
detach("package:stats", unload=TRUE)
detach("package:graphics", unload=TRUE)
detach("package:grDevices", unload=TRUE)
detach("package:utils", unload=TRUE)
library(utils)
install.packages()
现在可以工作。
当然,我们也需要重新加载其他库。考虑到依赖关系,使用
library(stats)
应该重新加载所有内容。但是重新加载 graphics
库时会出现问题,至少在 Windows 上是这样:
library(graphics)
# Error in FUN(X[[i]], ...) :
# no such symbol C_contour in package path/to/library/graphics/libs/x64/graphics.dll
(重新)加载图形
库的正确方法是什么?
最佳答案
修补包中的函数是应该避免的低级操作,因为它可能会破坏执行环境的内部假设并导致不可预测的行为/崩溃。如果 tck/ESS 存在问题(我没有尝试重复),也许应该修复它,或者可能有解决方法。特别是要避免更改锁定的绑定(bind)。
如果您确实想在 install.packages
的开头/结尾运行一些代码,您可以使用 trace
。它将执行问题中提到的一些低级操作,但好处是,每当 R 的一些新内部发生变化时,您不必担心修复此问题。
trace(install.packages,
tracer=quote(cat("Starting install.packages\n")),
exit=quote(cat("Ending install packages.\n"))
)
相应地替换tracer
和exit
- 也许无论如何都不需要exit
,也许您不需要卸载包。尽管如此,trace
仍然是一个非常有用的调试工具。
我不确定这是否能解决您的问题 - 是否可以与 ESS 一起使用 - 但一般来说,您也可以将 install.packages
包装在您在工作区中定义的函数中:
install.packages <- function(...) {
cat("Entry.\n")
on.exit(cat("Exit.\n"))
utils::install.packages(...)
}
这确实是最干净的选择。
关于R:修补包函数并重新加载基础库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40252292/