动态命名要更新的对象

Dynamically naming objects to be updated

我已经定义了一个 S4 Class,它的插槽是一个列表。我已经编写了一个方法(基于 Genolini 对 S4 的介绍 - 第 10.2 节)来向该列表追加一个新条目:

setClass("MyClass",
         slots = c(entries = "list")
)
a1 <- new("MyClass", entries = list(1))

setGeneric(name="MyAppend",
           def=function(.Object, newEntry)
           {
             standardGeneric("MyAppend")
           }
)


setMethod(f = "MyAppend",
          signature = "MyClass",
          definition = function(.Object, newEntry){
            nameObject <- deparse(substitute(.Object)) 
            newlist <- .Object@entries  
            n  <- newlist %>% length 
            newlist[[n + 1]] <- newEntry  
            .Object@entries  <- newlist
            assign(nameObject, .Object, envir = parent.frame())
            return(invisible)
          }
)

如果我那么运行

MyAppend(a1, 2)
a1

我明白了

R>a1
An object of class "MyClass"
Slot "entries":
[[1]]
[1] 1

[[2]]
[1] 2

这是应该的。

但在我的应用程序中,我将生成要动态更新的对象的名称:

ObjectName <- paste0("a", 1)

然后我可以使用

将该名称变成对象本身
Object <- ObjectName %>% sym %>% eval

然后是str(Object)returns

Formal class 'MyClass' [package ".GlobalEnv"] with 1 slot   
..@ entries:List of 3
   .. ..$ : num 1
   .. ..$ : num 2  

这又是理所应当的。

但是当我运行

MyAppend(Object, 3)
Object
a1

我得到以下信息,表明 Object 已更新 a1 尚未更新。

R>Object
An object of class "MyClass"
Slot "entries":
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3


R>
R>a1
An object of class "MyClass"
Slot "entries":
[[1]]
[1] 1

[[2]]
[1] 2

请问我做错了什么?

问题是这一行:

Object <- ObjectName %>% sym %>% eval

并不像您认为的那样。右边的计算结果为对象 a1,因此与执行

没有什么不同
Object <- a1

但这创建了 a1copy,它不会创建 a1 的引用或指针或同义词。

可以通过传递您希望附加到您的对象的未评估名称来创建(某种)引用通用方法。如果您省略了 ObjectName %>% sym %>% evaleval 部分,那么对象将被分配 name a1,它可以作为对 [=] 的引用传递45=]对象a1.

然而,这给您带来了一个新问题:MyAppend 不知道如何处理 class name 的对象。因此,您需要编写一个合适的方法来处理名称:

setMethod(f = "MyAppend",
          signature = "name",
          definition = function(.Object, newEntry){
             stopifnot(class(eval(.Object)) == "MyClass")
             objname <- as.character(.Object)
             .Object <- eval(.Object)
             .Object@entries <- append(.Object@entries, newEntry)
             assign(as.character(objname), .Object, envir = parent.frame())
          }
)

现在让我们看看它是如何工作的:

a1 <- new("MyClass", entries = list(1))
a1
#> An object of class "MyClass"
#> Slot "entries":
#> [[1]]
#> [1] 1

MyAppend(a1, 2)
a1
#> An object of class "MyClass"
#> Slot "entries":
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2

Object <- paste0("a", 1) %>% sym()

MyAppend(Object, 3)
a1
#> An object of class "MyClass"
#> Slot "entries":
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2
#> 
#> [[3]]
#> [1] 3

我认为这就是您的意图。您可能希望考虑使用一种方法来分派字符串以简化此工作流程(您可以在方法内部使用 get 从作为字符串传递的名称中检索对象)


请注意,我也更改了您自己的功能;你不应该做 return(invisible),因为这个 return 是内置函数 invisible 的主体。只需完全删除 return 语句即可。您还可以使用内置函数 append,使 MyClass 的方法更简单一些:

 setMethod(f = "MyAppend",
          signature = "MyClass",
          definition = function(.Object, newEntry){
           nameObject <- deparse(substitute(.Object)) 
           .Object@entries <- append(.Object@entries, newEntry)
           assign(nameObject, .Object, envir = parent.frame())
          }
)

正如接受的答案所提到的,行

Object <- ObjectName %>% sym %>% eval

未按预期运行。

如果你想使用动态生成的名称,你需要使用的两个函数是 get(获取与名称相关联的对象)和 assign(分配给名称为你计算)。两者都有全面的帮助页面。

几乎没有其他方法可以工作(eval(parse(text=paste0(...))) 除外,但出于多种原因不推荐编程实践。