exportClasses实际上对S4类做了什么?

gmxoilav  于 2023-07-31  发布在  其他
关注(0)|答案(1)|浏览(114)

我试图理解命名空间是如何与S4类一起工作的,因此使用不同的导出指令将一个小示例放在一起。结果对我来说毫无意义,所以希望有人能帮助解释发生了什么?
我的包只有一个文件,包含以下代码:

B1 <- setClass( "Base_1", slots = list(x = "numeric"))
B2 <- setClass( "Base_2", slots = list(x = "numeric"))
B3 <- setClass( "Base_3", slots = list(x = "numeric"))
B4 <- setClass( "Base_4", slots = list(x = "numeric"))

字符串
然后在NAMESPACE文件中定义以下内容:

export(B1)
exportClasses(Base_1)
export(B2)
exportClasses(Base_3)


然后加载并尝试使用该包,如下所示:

library(tstpkg)

B1(x = 1)   # Runs fine
B2(x = 1)   # Runs fine
B3(x = 1)   # Errors (as expected)
B4(x = 1)   # Errors (as expected)

new("Base_1", x = 1)   # Runs fine
new("Base_2", x = 1)   # Runs fine - Was expecting to error
new("Base_3", x = 1)   # Runs fine
new("Base_4", x = 1)   # Runs fine - Was expecting to error

setClass("Derv_1", contains = "Base_1")   # Runs fine
setClass("Derv_2", contains = "Base_2")   # Runs fine - Was expecting to error
setClass("Derv_3", contains = "Base_3")   # Runs fine
setClass("Derv_4", contains = "Base_4")   # Runs fine - Was expecting to error


我预计new(<class>)setClass(..., contains=<class>)Base_2Base_4上会失败,因为这两个类都没有公开。有人能解释一下这里发生了什么吗?
(如果你想自己玩代码,我把所有代码都放在github仓库here中)

niwlg2el

niwlg2el1#

导出的类被放置在导出列表中,并存储在包命名空间中。下面是Matrix包1.6-0版本导出的类列表:

ns <- asNamespace("Matrix")
ns.exports <- getNamespaceInfo(ns, "exports")
cl1 <- sort(grep("^[.]__C__", names(ns.exports), value = TRUE))
cl1[1:20]
[1] ".__C__BunchKaufman"              ".__C__BunchKaufmanFactorization"
 [3] ".__C__CHMfactor"                 ".__C__CHMsimpl"                 
 [5] ".__C__CHMsuper"                  ".__C__Cholesky"                 
 [7] ".__C__CholeskyFactorization"     ".__C__CsparseMatrix"            
 [9] ".__C__LU"                        ".__C__Matrix"                   
[11] ".__C__MatrixFactorization"       ".__C__QR"                       
[13] ".__C__RsparseMatrix"             ".__C__Schur"                    
[15] ".__C__SchurFactorization"        ".__C__TsparseMatrix"            
[17] ".__C__abIndex"                   ".__C__atomicVector"             
[19] ".__C__compMatrix"                ".__C__corMatrix"

以下是在Matrix命名空间中定义但 * 未 * 导出的类列表:

cl0 <- setdiff(sort(grep("^[.]__C__", names(ns), value = TRUE)), cl1)
cl0
[1] ".__C__dCsparseMatrix" ".__C__determinant"    ".__C__geMatrix"      
 [4] ".__C__lCsparseMatrix" ".__C__mMatrix"        ".__C__nCsparseMatrix"
 [7] ".__C__numLike"        ".__C__replValueSp"    ".__C__seqMat"        
[10] ".__C__xMatrix"

包导出类,以定义其他包可以导入和不可以导入的内容。如果另一个包试图导入一个不是从你的包导出的类,那么在调用importIntoEnv的过程中,该包的安装将失败,错误如下:
类%s不是由“命名空间:%s”导出的
从其他包导入类的包将导入类的定义缓存在其命名空间的父环境中。它们也可以缓存 superclass 定义,但在命名空间本身,而不是其父。后一个缓存对于大多数使用S4的软件包维护者来说是未知的,并且容易变得陈旧;我在下面解释。
下面是Matrix1.6-0版导出和SeuratObject4.1.3版导入的类列表:

ns <- asNamespace("SeuratObject")
ns.imports <- getNamespaceInfo(ns, "imports")
ns.imports.Matrix <- c(ns.imports[names(ns.imports) == "Matrix"],
                       recursive = TRUE, use.names = FALSE)
cl1 <- sort(grep("^[.]__C__", ns.imports.Matrix, value = TRUE))
[1] ".__C__dgCMatrix"

就一个,很方便。下面是缓存类的定义:

cl1.def <- mget(cl1, parent.env(ns))
str(cl1.def, max.level = 3L)
List of 1
 $ .__C__dgCMatrix:Formal class 'classRepresentation' [package "methods"] with 11 slots
  .. ..@ slots     :List of 6
  .. ..@ contains  :List of 11
  .. ..@ virtual   : logi FALSE
  .. ..@ prototype :Formal class 'S4' [package ""] with 0 slots
 list()
  .. ..@ validity  :function (object)  
  .. ..@ access    : list()
  .. ..@ className : chr "dgCMatrix"
  .. .. ..- attr(*, "package")= chr "Matrix"
  .. ..@ package   : chr "Matrix"
  .. ..@ subclasses: list()
  .. ..@ versionKey:<externalptr> 
  .. ..@ sealed    : logi FALSE

下面是缓存的超类定义:

scl1 <- paste0(".__C__", sort(names(cl1.def[[1L]]@contains)))
scl1.def <- mget(scl1, ns, ifnotfound = list(NULL)) # NULL <=> unexported
str(scl1.def, max.level = 2L)
List of 11
 $ .__C__CsparseMatrix :Formal class 'classRepresentation' [package "methods"] with 11 slots
 $ .__C__Matrix        :Formal class 'classRepresentation' [package "methods"] with 11 slots
 $ .__C__compMatrix    :Formal class 'classRepresentation' [package "methods"] with 11 slots
 $ .__C__dCsparseMatrix: NULL
 $ .__C__dMatrix       :Formal class 'classRepresentation' [package "methods"] with 11 slots
 $ .__C__dsparseMatrix :Formal class 'classRepresentation' [package "methods"] with 11 slots
 $ .__C__generalMatrix :Formal class 'classRepresentation' [package "methods"] with 11 slots
 $ .__C__mMatrix       : NULL
 $ .__C__replValueSp   : NULL
 $ .__C__sparseMatrix  :Formal class 'classRepresentation' [package "methods"] with 11 slots
 $ .__C__xMatrix       : NULL

这里超类定义的缓存指向了SeuratObject中的一个bug:它导入了dgCMatrix,但它导出的超类都没有,即它的NAMESPACE

importClassesFrom(Matrix, dgCMatrix)


但其实应该

importClassesFrom(Matrix, dgCMatrix,
                  ## and the exported superclasses:
                  CsparseMatrix, Matrix, compMatrix, dMatrix, 
                  dsparseMatrix, generalMatrix, sparseMatrix)


结果是缓存在名称空间中的超类定义最终会变得过时。而dgCMatrix的定义是在 load 时(当命名空间的父环境被填充时)检索的,超类的定义是在 install 时(当命名空间本身被填充[并序列化]时)检索的。如果加载时可用的Matrix版本与安装时可用的版本不同,并且这些版本包含冲突的超类定义,则SeuratObject的用户可能会遇到问题-如果您不熟悉缓存机制,则很难调试。
可能应该在编写R扩展手册(这里)中说明,如果您使用某个类,那么您也隐式地使用它的超类,并且也必须导入它们。
这些陷阱只是维护人员在更改他们导出的类的定义时应该尝试保持向后兼容性的众多原因之一。
最后,为什么newsetClass“看到”未导出的类?在new的情况下,我认为部分原因是性能。示例化必须快速,并且“可导出性”的测试具有不小的成本。对于setClass,我不太确定。这绝对 * 看起来 * 像是一个bug,但我想更努力地考虑一下,甚至可能咨询R-devel邮件列表(也是关于修改WRE的)……which I've now done的数据。

相关问题