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

问题描述

假设我定义了一个带有两个插槽 'foo''a' 的 S4 类 'b',并定义了一个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"

然后我想从 'a' 的定义中删除'foo'

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

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


我对所有答案深表感谢!

以下解决方案的灵感来自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:您可以使用 check=FALSE 中的 slot<- 参数将插槽分配为 NULL,而不会触发错误。

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

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

还应该注意 ?slot 中的建议,即“用户在正常使用中不应设置 check=FALSE,因为生成的对象可能无效。”在这种情况下,它不会导致任何问题,因为该插槽随后会立即被移除。尽管如此,谨慎使用 check=False 标志还是有好处的,除非您确定自己了解自己在做什么。

选项2:从类定义中删除槽后直接删除属性可以实现更完整的删除:

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

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

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

,

@dww 的建议很漂亮,可以回答您的问题。但是,一个类的重点不就是保证它的成员(插槽)永远在那里吗?如果您不关心这一点,您可以改用任何内容 S3 类吗?对于 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',times = 1e3L))
x@b <- ""
format(object.size(x),units = 'auto')
#[1] "8.7 Kb"

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