Классы R S4 с одинаковыми именами из разных пакетов

Предположим, что есть два пакета.

Package_A имеет этот класс:

setClass("Person", 
         slots = c(
           name = "character", 
           age = "numeric"
         )
)

setGeneric("age", function(x) standardGeneric("age"))
setMethod("age", "Person", function(x) x@age)

Package_B имеет аналогичный класс:

setClass("Person", 
         slots = c(
           name = "character", 
           age = "numeric"
         )
)

setGeneric("age", function(x) standardGeneric("age"))
setMethod("age", "Person", function(x) x@age * 10) # notice the difference here

Итак, пользователь загрузил оба пакета в свою рабочую среду:

library(Package_A)
library(Package_B)

В рабочей среде этого пользователя, как R разрешает путаницу с созданием объекта «Человек»:

john <- new("Person", name = "John Smith", age = 7)

В рабочей среде этого пользователя, как R разрешает вызов правильного метода:

age(john)

person free_lions_n_tigers_from_cages    schedule 17.03.2020    source источник
comment
Год и никаких ответов! Я тоже хочу знать ответ! Если функция myFunc существует в обоих пакетах, то A::myFunc() и B::myFunc() допускают устранение неоднозначности. Но setClass(), new() и т. д. используют строки для имен классов и, похоже, не понимают префиксы пакетов. Нигде не могу найти на это ответ.   -  person Stuart R. Jefferys    schedule 20.03.2021


Ответы (1)


Доступ к классам из определенного пакета

getClass()

new() принимает объект classRepresentation, который вы можете получить используя getClass. Например, new(getClass('className', where='packageName')). Однако обратите внимание, что это не будет работать, если вы еще не импортировали пакет, а также определили новый класс с тем же именем. Я демонстрирую эту проблему здесь:

install.packages('lookupTable')
setClass('lookupTable', slots = c(notworking='numeric'))
new(getClass('lookupTable', where='lookupTable'))
#> An object of class "lookupTable"
#> Slot "notworking":
#> numeric(0)

(он напечатал нерабочий слот, что означает, что он создает экземпляр моего пользовательского класса, а не правильной версии пакета)

new(classNameWithAttribute)

В new есть странная, но задокументированная функция, которая позволяет вам установить атрибут package для имени класса, который на самом деле работает отлично (т.е. не имеет проблемы, упомянутой выше), если немного подробно:

name = 'className'
attr(name, 'package') = 'packageName'
new(name)

Однако нет причин, по которым вы не могли бы превратить это в функцию многократного использования:

new = function(cls, pkg, ...){
    attr(cls, 'package') = pkg
    methods::new(cls, ...)
}
new('className', 'packageName')

Хороший дизайн упаковки

Конечно, всего этого можно избежать, если упаковщики классов S4 предусмотрят один из двух механизмов:

Экспорт значения setClass()

setClass() имеет как побочный эффект (сохранение класса в реестре), так и возвращаемое значение (функция генератора классов). Поэтому, если упаковщик решит сохранить и экспортировать возвращаемое значение в свой NAMESPACE, мы сможем получить к нему доступ позже:

# In package "myPackage"
myClass = setClass('myClass')

# In package's NAMESPACE
export(myClass)

# In user's script
new(myPackage::myClass)

Например, вы можете проверить это с помощью того же тестового пакета, что и раньше:

install.packages('lookupTable')
new(lookupTable::lookupTable)

Экспорт функции-конструктора

Это стандартная практика в биокондукторе. Они определяют функцию-конструктор с тем же именем, что и сам класс. Затем вы можете получить доступ к функции конструктора, используя ::, и вызвать ее вместо new:

install.packages("BiocManager")
BiocManager::install("IRanges")

new("IRanges")
# Equivalent to 
IRanges::IRanges()
person Migwell    schedule 30.03.2021