R:如何真正从 S4 对象中删除 S4 插槽(附有解决方案!)

R: How to truly remove an S4 slot from an S4 object (Solution attached!)

假设我定义了一个带有两个插槽 'a''b' 的 S4 class 'foo',并定义了 [=56= 的对象 x ] 'foo',

setClass(Class = 'foo', slots = c(
  a = 'numeric',
  b = 'character'
))
x <- new('foo', a = rnorm(1e3L), b = rep('A', times = 1e3L))
format(object.size(x), units = 'auto') # "16.5 Kb"

然后我想从 'foo'

的定义中删除插槽 'a'
setClass(Class = 'foo', slots = c(
  b = 'character'
))
slotNames(x) # slot 'a' automatically removed!! wow!!!

我看到 R 自动处理我的对象 x 并删除了插槽 'a'。好的!但是等等,对象 x 的大小并没有减少。

format(object.size(x), units = 'auto') # still "16.5 Kb"
format(object.size(new(Class = 'foo', x)), units = 'auto') # still "16.5 Kb"

对..不知何故 'a' 仍然存在,但我无法对它做任何事情

head(x@a) # `'a'` is still there
rm(x@a) # error
x@a <- NULL # error

所以问题:我怎样才能 真正x 中删除插槽 'a' 减小其大小 (这是我最关心的问题)?


非常感谢所有的回答!

以下解决方案的灵感来自dww

trimS4slot <- function(x) {
  nm0 <- names(attributes(x))
  nm1 <- names(getClassDef(class(x))@slots) # ?methods::.slotNames
  if (any(id <- is.na(match(nm0, table = c(nm1, 'class'))))) attributes(x)[nm0[id]] <- NULL  # ?base::setdiff
  return(x)
}
format(object.size(y1 <- trimS4slot(x)), units = 'auto') # "8.5 Kb"

以下解决方案的灵感来自Robert Hijmans

setClass('foo1', contains = 'foo')
format(object.size(y2 <- as(x, 'foo1')), units = 'auto') # "8.5 Kb"

method::as 可能会做一些全面的检查,所以速度很慢

library(microbenchmark)
microbenchmark(trimS4slot(x), as(x, 'foo1')) # ?methods::as 10 times slower

插槽存储为属性。我们有几个选项可以将插槽转换为 NULL.

选项 1:您可以使用 slot<- 中的 check=FALSE 参数将插槽分配为 NULL 而不会触发错误。

slot(x, 'a', check=FALSE) <- NULL
setClass(Class = 'foo', slots = c(b = 'character'))
format(object.size(x), units = 'auto') 
# [1] "8.7 Kb"

但是,该属性并未完全 删除(它仍然存在,值为 [=15=]1NULL[=15=]1)。发生这种情况是因为 C 函数 R_do_slot_assign 中的一行具有: if(isNull(value)) value = pseudo_NULL; 其中 pseudo_NULL 是“一个对象,用于表示为 NULL 的槽(这是一个属性不能)".

还应注意 ?slot 中的建议,即“用户不应在正常使用中设置 check=FALSE,因为生成的对象可能无效。”在这种情况下应该不会引起任何问题,因为插槽会在之后立即被删除。尽管如此,在使用 check=False 标志时要谨慎,除非你确定你明白你在做什么。

选项2:更彻底的移除可以通过在class定义中移除槽后直接移除属性来实现:

setClass(Class = 'foo', slots = c(
  b = 'character'
))
attr(x, 'a') <- NULL
format(object.size(x), units = 'auto') 
# [1] "8.7 Kb"

但是,删除插槽是个好主意吗?

删除插槽有点像 hack,可能会在以后导致错误,例如如果调用的方法假定插槽存在。您可能会在自己的机器上针对特定用例执行此操作。但是将其作为生产代码发布到野外并不是一个好主意。在那种情况下,@RobertHijmans 的回答中的方法就是可行的方法。

@dww 的建议很巧妙,可以回答您的问题。但是 class 的意义不是保证它的成员(插槽)将永远存在吗?如果你不关心,你可以使用 anything goes S3 classes 代替?对于 S4,我建议采用更正式的方法,例如:

setClass(Class = 'bar', slots = c(b = 'character'))

setClass(Class = 'foo', contains='bar', slots = c(a = 'numeric'))

x <- new('foo', a = rnorm(1e3L), b = rep('A', times = 1e3L))
format(object.size(x), units = 'auto')
#[1] "16.5 Kb"

x <- as(x, "bar")  
format(object.size(x), units = 'auto')
#[1] "8.5 Kb"

如果这只是大小问题,为什么不直接做呢

x <- new('foo', a = rnorm(1e3L), b = rep('A', times = 1e3L))
x@b <- ""
format(object.size(x), units = 'auto')
#[1] "8.7 Kb"

对我来说,这显然是最好的解决方案,因为它很简单。