Что на самом деле делает ExportClasses с классами S4?
Я пытался понять, как пространство имен работает с классами S4, поэтому составил небольшой пример, используя различные директивы экспорта. Результаты не имеют для меня никакого смысла, поэтому я надеялся, что кто-нибудь сможет помочь объяснить, что происходит?
Мой пакет состоит всего из 1 файла со следующим кодом:
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_2
иBase_4
поскольку ни один из этих классов не был раскрыт. Кто-нибудь может объяснить, что здесь происходит?
(Я поместил весь код в репозиторий GitHub здесь , если вы хотите сами поиграть с кодом)
1 ответ
Классы, которые вы экспортируете, помещаются в список экспорта, хранящийся в пространстве имен вашего пакета. Вот список классов, экспортированных пакетом 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»
Пакеты, которые импортируют классы из других пакетов, кэшируют определения импортированных классов в родительской среде своего пространства имен. Они также могут кэшировать определения суперкласса , но в самом пространстве имен, а не в его родительском пространстве. Последний кэш неизвестен большинству разработчиков пакетов, использующих S4, и может устареть; Я объясняю ниже.
Вот список классов, экспортированных Matrix версии 1.6-0 и импортированных SeuratObject версии 4.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 : он импортирует, но ни один из экспортированных суперклассов, т.е.NAMESPACE
имеет
importClassesFrom(Matrix, dgCMatrix)
но действительно должен был бы иметь
importClassesFrom(Matrix, dgCMatrix,
## and the exported superclasses:
CsparseMatrix, Matrix, compMatrix, dMatrix,
dsparseMatrix, generalMatrix, sparseMatrix)
Следствием этого является то, что определения суперкласса, кэшированные в пространстве имен, со временем могут устареть. В то время как определениеdgCMatrix
извлекается во время загрузки (когда заполняется родительская среда пространства имен), определения суперклассов извлекаются во время установки (когда само пространство имен заполняется [и сериализуется]). Если версия Matrix , доступная во время загрузки, отличается от версии, доступной во время установки, и если эти версии содержат конфликтующие определения суперкласса, то пользователи SeuratObject могут столкнуться с проблемами, которые довольно сложно отладить, если вы еще не знакомы с механизм кэширования.
Вероятно, в руководстве по написанию расширений R ( здесь ) следует задокументировать, что если вы используете какой-либо класс, вы также неявно используете его суперклассы и также должны импортировать их.
Эти ловушки — лишь одна из многих причин, по которым сопровождающие должны стараться сохранять обратную совместимость при изменении определений экспортируемых классов.
Наконец, зачем делать и «видеть» неэкспортированные классы? В случаеnew
Я полагаю, что отчасти причина в производительности. Создание экземпляра должно быть быстрым, а проверка на «экспортируемость» требует нетривиальных затрат. В случаеsetClass
, Я не совсем уверен. Это определенно похоже на ошибку, но мне бы хотелось подумать об этом немного тщательнее и, возможно, даже проконсультироваться со списком рассылки R-devel (также по поводу внесения поправок в WRE)... что я сейчас и сделал .