一个未导入的方法在未连接的包中如何被调用函数所找到,而这些函数没有其命名空间中?

30
一个R命名空间是其关联包中所有函数的立即环境。换句话说,当来自包foo的函数bar()调用另一个函数时,R评估器首先在中搜索其他函数,然后在"imports.foo"、、等中搜索搜索列表,该列表可通过键入search()返回。
命名空间的一个好处是它们可以使包表现得更像良好的公民:在中未导出的函数和在imports:foo中的函数仅对foo的函数、从foo导入的其他包或通过完全限定函数调用(如foo:::bar())可用。
或者我最近这样认为...
行为

最近的一个Stack Overflow问题突出了这样一种情况,即一个在其包命名空间中很好隐藏的函数却被一个看似无关的函数调用找到:

group <- c("C","F","D","B","A","E")
num <- c(12,11,7,7,2,1)
data <- data.frame(group,num)

## Evaluated **before** attaching 'gmodels' package
T1 <- transform(data, group = reorder(group,-num))

## Evaluated **after** attaching 'gmodels
library(gmodels)
T2 <- transform(data, group = reorder(group,-num))

identical(T1, T2) 
# [1] FALSE

它的直接原因

@Andrie通过指出gmodels从包gdata中导入,该包包括一个函数reorder.factor,该函数在第二次调用transform()内部被调度。 T1T2的区别在于第一个是由stats:::reorder.default()计算的,而第二个是由gdata:::reorder.factor()计算的。

我的问题

在上面对transform(data, group=reorder(...))的调用中,reorder的调度机制如何找到并调度到gdata:::reorder.factor()

(答案应包括解释从涉及statsbase包中的函数的调用到看似隐藏的gdata方法的作用域规则。)


更多可能有帮助的细节

  1. Neither gdata:::reorder.factor, nor the gdata package as a whole are explicitly imported by gmodels. Here are the import* directives in gmodels' NAMESPACE file:

    importFrom(MASS, ginv)
    importFrom(gdata, frameApply)
    importFrom(gdata, nobs)
    
  2. There are no methods for reorder() or transform() in <environment: namespace:gmodels>, nor in "imports:gmodels":

    ls(getNamespace("gmodels"))
    ls(parent.env(getNamespace("gmodels")))
    
  3. Detaching gmodels does not revert reorder()'s behavior: gdata:::reorder.factor() still gets dispatched:

    detach("package:gmodels")
    T3 <- transform(data, group=reorder(group,-num))
    identical(T3, T2)
    # [1] TRUE
    
  4. reorder.factor() is not stored in the list of S3 methods in the base environment:

    grep("reorder", ls(.__S3MethodsTable__.))
    # integer(0)
    

最近几天的R聊天记录中包含了一些额外的想法。感谢Andrie、Brian Diggs和Gavin Simpson(与其他人一起)可以自由编辑或添加可能重要的细节到这个问题中。


5
我看到这是你的第一个问题。欢迎来到SO! :) - Roman Luštrik
+1 是为了表扬你对自己问题的良好管理。 - Brandon Bertelsen
1
@BrandonBertelsen -- 你认为你的问题让你发疯了!在我意识到“哦,傻瓜,我应该在SO上问一下,让更好的头脑帮我解决这个问题!”之前,我花了几天时间来弄清楚这里发生了什么。 - Josh O'Brien
2
我学会了在30分钟后放弃。用打勾和点赞的方式支付人更划算 :) - Brandon Bertelsen
1个回答

18

我不确定我是否正确理解了你的问题,但主要问题是group是字符向量而data$group是因子。

在加载gmodels后,对reorder(factor)的调用将调用gdata:::reorder.factor。因此,reorder(factor(group))会调用它。

transform中,该函数在第一个参数的环境中计算,因此在T2 <- transform(data, group = reorder(group,-num))中,group是因子。

已更新

library将导入的软件包附加到已加载命名空间中。

> loadedNamespaces()
 [1] "RCurl"     "base"      "datasets"  "devtools"  "grDevices" "graphics"  "methods"  
 [8] "stats"     "tools"     "utils"    
> library(gmodels) # here, namespace:gdata is loaded
> loadedNamespaces()
 [1] "MASS"      "RCurl"     "base"      "datasets"  "devtools"  "gdata"     "gmodels"  
 [8] "grDevices" "graphics"  "gtools"    "methods"   "stats"     "tools"     "utils"    

以防万一,namespace:stats中存在reorder通用函数:

> r <- ls(.__S3MethodsTable__., envir = asNamespace("stats"))
> r[grep("reorder", r)]
[1] "reorder"            "reorder.default"    "reorder.dendrogram"

更多细节请参考:

reorder 函数的调用将在两个环境中搜索 S3 泛型函数:

请参考 ?UseMethod

首先在调用泛型函数的环境中搜索,然后在定义泛型的注册数据库所在的环境(通常是命名空间)中搜索。

loadNamespace 函数会将 S3 函数注册到命名空间中。

所以,在你的情况下,library(gmodels) -> loadNamespace(gdata) -> registerS3Methods(gdata)

在此之后,你可以通过以下方法找到它:

> methods(reorder)
[1] reorder.default*    reorder.dendrogram* reorder.factor*    

   Non-visible functions are asterisked

然而,由于 reorder.factor 并未在您的搜索路径上附加,因此您无法直接访问它:

> reorder.factor
Error: object 'reorder.factor' not found

可能这就是整个情况。


你能解释一下从?UseMethod中提到的两个环境开始,搜索函数时如何在gdata的命名空间中查找吗?它已经被加载了,但我以为它不在搜索路径上(我以为这是加载和附加之间的区别)。 - Brian Diggs
1
@BrianDiggs -- 这是发生的事情。当加载gdata时,它定义的所有方法都会在各种不同命名空间的数据库中“注册”。规则是一个方法在定义其S3通用程序的包的命名空间中注册。由于reorder是在stats中定义的,因此它在称为.__S3MethodsTable__.的环境中注册。看看这个:library(gmodels); head(ls(getNamespace("stats"), all=TRUE)); ls(stats:::.__S3MethodsTable__., pattern="reorder"); get("reorder.factor", stats:::.__S3MethodsTable__.)。很酷,对吧? - Josh O'Brien
@BrianDiggs -- 这意味着,如果您从一个包中importFrom()了一个函数,那么您也会注册该包中的所有方法。(虽然我可以理解R核心团队为什么这样做,但这是一个相当意外的副作用。)这也解释了为什么卸载gmodels对调用哪个reorder()方法没有影响:卸载不会清理加载包时注册的所有方法。(事实上,即使unloadNamespace()也不会进行清理。我想知道是否有一些函数可以做到这一点...) - Josh O'Brien
@JoshO'Brien 感谢您的澄清。值得注意的是,ls(.__S3MethodsTable__., envir=asNamespace("stats"))ls(stats:::.__S3MethodsTable__.)不同(前者在kohske的答案中,后者在您的评论中)。后者似乎更合适,并显示方法函数在通用名称空间中加载,而不考虑原始名称空间。此外,我认为我听说过(但现在无法引用)没有完全保证一旦加载就可以完全分离包的方法;这可能是其中之一的原因。 - Brian Diggs
@BrianDiggs -- 是的。ls(.__S3MethodsTable__., envir=asNamespace("stats"))基本上就是ls(asNamespace("stats"),因为.GlobalEnv中没有.__S3MethodsTable__.。我敢打赌你说得也对,这可能是无法可靠地完成分离的原因(或许是the 原因)。 - Josh O'Brien

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接