Динамическое именование объектов для обновления - PullRequest
1 голос
/ 20 марта 2020

Я определил класс S4 со слотом, который является списком. Я написал метод (основанный на введении Дженолини в 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) возвращает

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

Что я делаю не так, пожалуйста

Ответы [ 2 ]

1 голос
/ 21 марта 2020

Проблема в том, что эта строка:

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

не делает то, что вы думаете, что делает. Правая часть оценивает объект a1, поэтому он ничем не отличается от выполнения

Object <- a1

Но это создает копию из a1, но не создает ссылку или указатель или синоним для a1.

Это можно создать ссылку (своего рода), передав неоцененное имя объекта, который вы будете sh для добавления к вашему методу c. Если вы пропустите eval часть ObjectName %>% sym %>% eval, тогда Объекту будет присвоено имя a1, которое можно передать как ссылку на объект a1.

Однако это ставит вас перед новой проблемой: MyAppend не знает, что делать с объектом класса 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

Я думаю, это было то, что вы хотели. Вы можете sh рассмотреть возможность использования метода, который отправляет символьные строки, чтобы упростить этот рабочий процесс (вы должны использовать get внутри метода для извлечения объекта из имени, переданного в виде символьной строки)


Обратите внимание, что я также изменил вашу собственную функцию; вы не должны делать return(invisible), так как это возвращает тело встроенной функции invisible. Просто оставьте ответное заявление вообще. Вы также можете использовать встроенную функцию 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())
          }
)
0 голосов
/ 23 марта 2020

Как уже упоминалось в принятом ответе, строка

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

не работает так, как вы предполагаете.

Если вы хотите работать с динамически генерируемыми именами, две функции, которые вам необходимо используются get (получить объект, связанный с именем) и assign (назначить объекту, имя которого вы вычисляете). У обоих есть полные справочные страницы.

Практически ничего больше не будет работать (кроме eval(parse(text=paste0(...))), но по ряду причин это не рекомендуется для практики программирования.

...