【问题标题】:Modify S3 object without returning it?修改 S3 对象而不返回它?
【发布时间】:2014-01-20 20:19:00
【问题描述】:

我是 R 中面向对象编程的新手,并且不知道如何正确编写修改对象的函数。

这个例子有效:

store1 <- list(
  apples=3,
  pears=4,
  fruits=7
)
class(store1) <- "fruitstore"
print.fruitstore <- function(x) {
  paste(x$apples, "apples and", x$pears, "pears", sep=" ")
}
print(store1)
addApples <- function(x, i) {
x$apples <- x$apples + i
x$fruits <- x$apples + x$pears
return(x)
}
store1 <- addApples(store1, 5)
print(store1)

但我认为应该有一种更简洁的方法来做到这一点,而无需返回整个对象:

addApples(store1, 5)  # Preferable line...
store1 <- addApples(store1, 5)  # ...instead of this line

在 R 中编写修改函数的正确方法是什么? “

更新:感谢大家为 R 中的 OOP 带来的 Rosetta Stone。非常有用。 我试图解决的问题在流程方面非常复杂,因此参考类的刚性可能会带来结构的帮助。我希望我能接受所有回复作为答案,而不仅仅是一个。

【问题讨论】:

  • 如果你真的想就地修改,那么也许你不应该使用 S3 对象,而是使用reference class 对象。
  • 好评论。引用类的目的突然变得有意义了。

标签: r r-s3


【解决方案1】:

如果您不想深入参考类,您实际上可以使用具有替换功能的 S3 类来做到这一点。首先,你的例子

store1 <- list(apples=3,pears=4)
class(store1) <- "fruitstore"
print.fruitstore <- function(x) {
  x <- paste(unlist(store1), names(store1), collapse=", ")
  x <- paste0(x, " for a total of ", sum(unlist(store1)), " fruit.")
  NextMethod()
}
store1
# [1] "3 apples, 4 pears for a total of 7 fruit."

注意使用NextMethod 意味着我不必执行print(store1),我只需输入store。基本上,一旦我将x 重新指定为我想要在屏幕上显示的内容,我只需发送默认的print 方法。那么:

`addapples<-` <- function(x, ...) UseMethod("addapples<-")
`addapples<-.fruitstore` <- function(x, value) {
  x[["apples"]] <- x[["apples"]] + value
  x
}
addapples(store1) <- 4
store1
# [1] "7 apples, 4 pears for a total of 11 fruit."

多田!同样,并不是典型的 S3 用例。除了[[[ 函数之外,替换函数通常用于更新属性(例如类、长度等),但我认为拉伸它并没有太大的危害。

请注意,这不是真正的引用分配。实际上,您的 fruitstore 对象被复制、修改并重新分配给原始变量(请参阅 R Docs)。

【讨论】:

    【解决方案2】:

    这是一个参考类实现,正如其中一个 cmets 中所建议的那样。基本思想是建立一个名为Stores 的引用类,它具有三个字段:applespearsfruits(编辑为访问器方法)。 initialize 方法用于初始化一个新的 store,addApples 方法将苹果添加到 store,而 show 方法相当于 print 用于其他对象。

    Stores = setRefClass("Stores", 
      fields = list(
        apples = "numeric",
        pears  = "numeric",
        fruits = function(){apples + pears}
      ), 
      methods = list(
        initialize = function(apples, pears){
          apples <<- apples
          pears <<- pears
        },
        addApples = function(i){
          apples <<- apples + i
        },
        show = function(){
          cat(apples, "apples and", pears, "pears")
        }
      )
    )
    

    如果我们初始化一个新的 store 并调用它,这就是我们得到的

    FruitStore = Stores$new(apples = 3, pears = 4)
    FruitStore
    
    # 3 apples and 4 pears
    

    现在,调用addApples 方法,让我们向商店添加4 个苹果

    FruitStore$addApples(4)
    FruitStore
    
    # 7 apples and 4 pears
    

    编辑。根据 Hadley 的建议,我已经更新了我的答案,以便 fruits 现在是一种访问器方法。随着我们向商店添加更多 apples,它会保持更新。谢谢@hadley。

    【讨论】:

    • 似乎fruits 不应该是一个字段,而是一个计算方法(或者如果你想真正喜欢你可以做一个访问器方法,所以FruitStore$fruit 仍然可以工作)
    • 是的。我已经使用访问器方法更新了我的答案。
    【解决方案3】:

    你应该看看data.table package

    加载包:library(data.table)

    定义一个data.table对象:

    store1 <- data.table(apples=3,
                     pears=4,
                     fruits=7)
    

    然后定义函数addApple:

     addApple <- function(data,i) {data[,apples:=(data[1,apples]+i)]; 
                                 data[,fruits:=(data[1,apples]+data[1,pears])]}
    

    就是这样。

    当你写 addApple(store1) 时,你应该得到 +i apples 和 "apples + pears" 水果。

    如果你愿意,你仍然可以定义你的 S3 类,只要确保它继承了 data.frame 和 data.table 类。

    class(store1) <- c("fruitstore",class(store1))
    

    还有一件事,你应该重写你的 S3 打印方法,让 print 显式:

    print.fruitstore <- function(x) {
       print(paste(x$apples, "apples and", x$pears, "pears", sep=" "))
    }
    

    或者你可以只使用 cat:

    print.fruitstore <- function(x) {cat(x$apples, "apples and", x$pears, "pears")}
    

    【讨论】:

      【解决方案4】:

      这是一个使用proto package 的实现,它将对象和类统一为原型的单一概念。例如,Fruitstore 是一个扮演类角色的对象,store1 是一个扮演特定商店角色的对象。它们都是原型对象。

      library(proto)
      
      Fruitstore <- proto(
          addApples = function(., i) {
              .$apples <- .$apples + i
              .$fruits <- .$apples + .$pears
          },
          print = function(.) cat(.$apples, "apples and", .$pears, "pears\n")
      )
      
      # define store1 as a child of Fruitstore
      store1 <- Fruitstore$proto(apples = 3, pears = 4, fruits = 7)
      
      store1$addApples(5)
      store1$print()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-13
        • 1970-01-01
        • 2023-03-25
        • 1970-01-01
        • 1970-01-01
        • 2019-06-12
        相关资源
        最近更新 更多