Что на самом деле делает 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)... что я сейчас и сделал .

Другие вопросы по тегам