我有一堆函数,我试图通过在一个环境中定义它们并附加环境来保持工作区的整洁,其中一些函数是S3泛型,它们似乎不适合这种方法。
我所遇到的最小示例需要4个文件:
试验机R
ttt.xxx <- function(object) print("x")
ttt <- function(object) UseMethod("ttt")
ttt2 <- function() {
yyy <- structure(1, class="xxx")
ttt(yyy)
}
在testfun.R中,我定义了一个S3泛型ttt和一个方法ttt.xxx,我还定义了一个调用泛型的函数ttt2。
试验剂R
test_env <- new.env(parent=globalenv())
source("testfun.R", local=test_env)
attach(test_env)
在testenv.R中,我将testfun.R源代码添加到一个环境中,并附加到该环境中。
测试1.R
source("testfun.R")
ttt2()
xxx <- structure(1, class="xxx")
ttt(xxx)
test1.R将testfun.R提供给全局环境。ttt2和直接函数调用都可以工作。
测试2.R
source("testenv.R")
ttt2()
xxx <- structure(1, class="xxx")
ttt(xxx)
test2.R使用“attach”方法。ttt2仍然工作(并将“x”打印到控制台),但直接函数调用失败:
Error in UseMethod("ttt") :
no applicable method for 'ttt' applied to an object of class "xxx"
但是,不带参数调用ttt和ttt.xxx表明它们是已知的,ls(pos=2)表明它们在搜索路径上,sloop::s3_dispatch(ttt(xxx))告诉我它应该工作。
这个问题与Confusion about UseMethod search mechanism和其中的链接https://blog.thatbuthow.com/how-r-searches-and-finds-stuff/有关,但我无法理解正在发生的事情:为什么它不起作用,我怎样才能让它起作用。
我已经尝试了R Studio和R in the shell。
更新:根据以下答案,我将我的测试版本变更为:
test_env <- new.env(parent=globalenv())
source("testfun.R", local=test_env)
attach(test_env)
if (is.null(.__S3MethodsTable__.))
.__S3MethodsTable__. <- new.env(parent = baseenv())
for (func in grep(".", ls(envir = test_env), fixed = TRUE, value = TRUE))
.__S3MethodsTable__.[[func]] <- test_env[[func]]
rm(test_env, func)
...这是有效的(我只使用“.”作为S3调度分隔符)。
2条答案
按热度按时间kyks70gy1#
一个鲜为人知的事实是,您必须使用
.S3method()
为定制环境内(包外)的S3泛型定义方法。1几乎没有人知道这一点的原因是因为它在全局环境中是 * 不 * 必要的;但从R3.6版开始,在其他任何地方都必须这样做。实际上没有关于这个变化的文档,只有a technical blog post by Kurt Hornik about some of the background。注意,博客文章说这个变化是在R3.5.0中做的;然而,您所观察到的实际效果--S3方法不再在连接环境中搜索--只是在R3.6.0中才开始出现;在此之前,它不知怎么还没有激活。
...除了仅仅使用
.S3method
* 不会 * 修复你的代码,因为你的 * 调用环境 * 是全局环境。我不明白为什么这个不起作用的确切原因,我怀疑这是由于R的S3方法查找中的一个微妙的bug。事实上,使用getS3method('ttt', 'xxx')
* 确实 * 起作用,即使它应该与实际的S3方法查找具有相同的行为。我发现,实现此功能的唯一方法是将以下代码添加到
testenv.R
:...换句话说:手动为
.GlobalEnv
提供S3方法查找表。不幸的是,这依赖于未记录的S3实现细节,理论上将来可能会更改。或者,如果您使用‘box’ modules而不是
source
,它“只是工作”,也就是说,您可以将整个testenv.R
替换为以下内容:这段代码将
testfun.R
视为一个 * 本地模块 * 并加载它,附加所有导出的名称(通过attach声明[...]
)。1(在包内部,您需要使用等效的
S3method
命名空间声明,尽管如果您使用'roxygen 2',那么它会为您处理)wljmcqd82#
首先,我的建议是:不要试图重新发明R软件包。它们解决了所有你说要解决的问题,也解决了其他问题。
其次,我将尝试解释
test2.R
中的错误,它在xxx
对象上调用ttt
,并且ttt.xxx
在搜索列表中,但是没有找到。问题是搜索
ttt.xxx
是如何发生的。搜索 * 不 * 在搜索列表中查找ttt.xxx
,而是在调用ttt
的环境中查找,然后在名为.__S3MethodsTable__.
的对象中查找。我认为有两个原因:因为你对
ttt()
的调用发生在顶层,R首先在顶层查找ttt.xxx()
,但是没有找到,然后在全局.__S3MethodsTable__.
(实际上在基础环境中)查找,也没有找到,所以失败了。有一种变通方法可以使您的代码正常工作。如果运行
作为testenv.R的最后一行,那么您将在全局环境中创建一个方法表。(通常那里没有方法表,因为那是用户空间,R不喜欢把东西放在那里,除非用户要求。)R将找到那个方法表,并将找到它定义的
ttt.xxx
方法。如果这破坏了S3调度的其他方面,我不会感到惊讶。所以我不建议这么做,但是如果你坚持要重新发明软件包系统,那就试试吧。