如果我有一个S4类,如:
setClass("MyClass",
representation(
data="data.frame",
name="character"))
并实例化它(比如obj
),
obj <- new('MyClass', data=data.frame(1:3), name='An S4 class')
我将有以下表示:
An object of class "MyClass"
Slot "data":
X1.3
1 1
2 2
3 3
Slot "name":
[1] "An S4 class"
到目前为止一切顺利。
但是,如果我尝试使用:
更改“类”class(obj) <- "animal"
我现在得到了
An object of class "animal"
<S4 Type Object>
attr(,"data")
X1.3
1 1
2 2
3 3
attr(,"name")
[1] "An S4 class"
如果我尝试检查它是否仍然是S4类,它将返回true:
>isS4(obj)
[1] TRUE
到底发生了什么?为什么“老虎机”改为属性?这真的还是S4级吗?
更新
感谢您提供全面的答案。只是为了澄清,我并不期望这可以工作或在任何真实场景中使用。我只是想更好地理解这种行为背后的机制。 此外,很难选择一个“最佳”的答案(它们都很优秀)但是,在SO的精神下,我必须选择一个。
答案 0 :(得分:3)
S4将插槽实现为属性。这通常对用户隐藏,但很容易看到
> attributes(setClass("MyClass", representation(x="integer"))())
$x
integer(0)
$class
[1] "MyClass"
attr(,"package")
[1] ".GlobalEnv"
在更多的血腥细节中,我们有
> .Internal(inspect(setClass("MyClass", representation(x="integer"))()))
@1fe4dfd8 25 S4SXP g0c0 [OBJ,NAM(2),S4,gp=0x10,ATT]
ATTRIB:
@1fe4dfa0 02 LISTSXP g0c0 []
TAG: @23c8978 01 SYMSXP g0c0 [MARK,NAM(2)] "x"
@1fe4df68 13 INTSXP g0c0 [] (len=0, tl=0)
TAG: @2363208 01 SYMSXP g0c0 [MARK,NAM(2),LCK,gp=0x4000] "class" (has value)
@1fd9f1b8 16 STRSXP g0c1 [NAM(2),ATT] (len=1, tl=0)
@2e09e138 09 CHARSXP g0c1 [gp=0x61] [ASCII] [cached] "MyClass"
ATTRIB:
@1fd9fb20 02 LISTSXP g0c0 []
TAG: @236cc00 01 SYMSXP g0c0 [MARK,NAM(2)] "package"
@1fd9f278 16 STRSXP g0c1 [NAM(2)] (len=1, tl=0)
@23cc938 09 CHARSXP g0c2 [MARK,gp=0x61] [ASCII] [cached] ".GlobalEnv"
这表明用于表示所有R对象的基础S表达式是S4SXP,附带了属性列表。
通过使用S3-ism class<-
,正如@hadley指出的那样,你创造了一个混合怪物。 class<-
只更新class属性,而不更改底层的S4SXP。当您打印对象时,它使用print方法打印“animal”类的对象,可能是print.default。另一方面,isS4测试S表达式是否为S4SXP。所以你有一些...
强迫,或许通过将相关的setAs
函数, using
实现为(obj,“animal”)`。
答案 1 :(得分:2)
询问什么是S4对象有点棘手。如果我们从R internals获取定义,是的,它仍然是S4对象,因为S4 bit
仍然设置。
obj <- new('MyClass', data=data.frame(1:3), name='An S4 class')
attr(obj, 'class')
## [1] "MyClass"
## attr(,"package")
## [1] ".GlobalEnv"
obj2 <- obj
class(obj2) <- 'animal'
attr(obj, 'class')
## [1] "MyClass"
请注意,obj
和obj2
之间唯一的区别(就内存表示而言)实际上是缺少与package
属性关联的class
属性。我们可以修复&#34;通过致电:
attr(class(obj2), "package") <- ".GlobalEnv"
但在这种情况下,我们也会得到相同的&#34;奇怪的&#34;结果:
print(obj2)
## An object of class "animal"
## <S4 Type Object>
## attr(,"data")
## X1.3
## 1 1
## 2 2
## 3 3
## attr(,"name")
## [1] "An S4 class"
因此,让我们寻找负责打印obj
和obj2
的方法。在这两种情况下,都是通过show
签名ANY
完成的。打印getMethod("show", "ANY")
会将我们发送到showDefault
功能。
showDefault
做的第一件事就是:
...
clDef <- getClass(cl <- class(object), .Force = TRUE)
...
你看,getClass在GlobalEnv中找不到animal
的正式类定义。
这就是它调用show(unclass(object))
并将所有内容视为属性的原因(参见print(unclass(obj))
)(编辑:为什么属性:在@MartinMorgan的答案中解释)。< / p>
答案 2 :(得分:2)
obj
将继续表现为S4对象,但请注意,除非animal
是已定义的S4,否则更改此混合对象中的插槽值将失败具有相同名称的槽的类。此外,更改插槽值的操作也会丢弃不在animal
中的任何插槽。
obj@data <- data.frame() # FAILS, animal not defined
setClass("animal", representation(data="data.frame"))
obj@data <- data.frame() # works, but drops name
正如@MartinMorgan所指出的,将一个S4类更改为另一个类的正确方法是使用setAs
注册转换函数,然后使用新类的名称在对象上调用as
:
# define animal with the same slots
setClass("animal", representation(data="data.frame", name="character"))
# register conversion function
setAs("MyClass", "animal", function(from, to )new(to, data=from@data, name=from@name))
# new obj
obj <- new('MyClass', data=data.frame(1:3), name='An S4 class')
as(obj, 'animal')