问题描述
假设我定义了一个带有两个插槽 '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"
对我来说,这显然是最好的解决方案,因为它很简单。