改变" class"会发生什么?使用类函数的S4对象?

时间:2014-05-08 13:36:11

标签: r oop s4

如果我有一个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的精神下,我必须选择一个。

3 个答案:

答案 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"

请注意,objobj2之间唯一的区别(就内存表示而言)实际上是缺少与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"

因此,让我们寻找负责打印objobj2的方法。在这两种情况下,都是通过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)

在添加类属性“animal”之后,

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')