Почему R капризно использует атрибуты для объектов ссылочного класса?

У меня возникли проблемы с достижением согласованного поведения при доступе к атрибутам, прикрепленным к объектам ссылочного класса. Например,

testClass <- setRefClass('testClass',
  methods = list(print_attribute = function(name) print(attr(.self, name))))
testInstance <- testClass$new()
attr(testInstance, 'testAttribute') <- 1
testInstance$print_attribute('testAttribute')

И консоль R весело печатает NULL. Однако, если мы попробуем другой подход,

testClass <- setRefClass('testClass',
  methods = list(initialize = function() attr(.self, 'testAttribute') <<- 1,
                 print_attribute = function(name) print(attr(.self, name))))
testInstance <- testClass$new()
testInstance$print_attribute('testAttribute')

и теперь у нас есть 1, как и ожидалось. Обратите внимание, что требуется оператор <<-, предположительно потому, что присвоение .self имеет те же ограничения, что и присвоение полям ссылочного класса. Обратите внимание: если бы мы пытались назначить вне конструктора, скажем,

testClass <- setRefClass('testClass',
  methods = list(set_attribute = function(name, value) attr(.self, name) <<- value,
                 print_attribute = function(name) print(attr(.self, name))))
testInstance <- testClass$new()
testInstance$set_attribute('testAttribute', 1)

нас бы ударили

Error in attr(.self, name) <<- value :
 cannot change value of locked binding for '.self'

Действительно, документация ?setRefClass объясняет, что

На весь объект в методе можно ссылаться по зарезервированному имени .self ... Эти поля доступны только для чтения (нет смысла изменять эти ссылки), за одним исключением. В принципе, поле .self можно изменить в методе $initialize, потому что объект все еще создается на этом этапе.

Меня все это устраивает, и я согласен с авторскими решениями. Однако меня беспокоит следующее. Возвращаясь к первому примеру выше, если мы попытаемся запросить attr(testInstance, 'testAttribute'), мы увидим из глобальной среды, что это 1!

Предположительно, .self, который используется в методах объекта ссылочного класса, хранится в той же области памяти, что и _16 _ - это тот же объект. Таким образом, успешно установив атрибут на testInstance в глобальной среде, но не в качестве .self ссылки (как показано в первом примере), не запустили ли мы случайно копию всего объекта в глобальной среде? Или способ хранения атрибутов в некотором роде «забавный»: объект может находиться в одной и той же памяти, но его атрибуты различаются в зависимости от вызывающей среды?

Я не вижу другого объяснения, почему attr(.self, 'testAttribute') равно NULL, а attr(testInstance, 'testAttribute') равно 1. Привязка .self заблокирована раз и навсегда, но это не означает, что объект, на который он ссылается, нельзя изменить. Если это желаемое поведение, это похоже на ошибку.

Последний вопрос заключается в том, подразумевают ли предыдущие результаты attr<- следует избегать использования объектов ссылочного класса, по крайней мере, если результирующие атрибуты используются из методов объекта.


person Robert Krzyzanowski    schedule 31.03.2014    source источник
comment
Почему вы пытаетесь установить атрибуты объекта ссылочного класса вместо использования полей ???   -  person hadley    schedule 01.04.2014
comment
Это отличный вопрос! Причина в том, что я предполагаю, что не владею объектом эталонного класса - это черный ящик. В частности, эта проблема возникла, когда я пытался написать древовидную структуру с утиным типом, в которой в качестве узлов использовались произвольные объекты R (т.е. агностики типа ООП). Теперь я мог бы написать вокруг него класс-оболочку, как пакет XML создает оболочку вокруг узлов XML, но мне не нравится, насколько запутанными и вложенными становятся подобные вещи. Кроме того, R уже имеет канонический способ указания атрибутов meta-data:. Я обнаружил, что, хотя они мне и не нужны, возможность указывать атрибуты на   -  person Robert Krzyzanowski    schedule 01.04.2014
comment
узлы, из которых состоит мое дерево, позволили провести неплохую оптимизацию. В любом случае, если кому-то дается черный ящик, такой как объект ссылочного класса, который он не писал и которому не должно быть разрешено изменять, он должен по-прежнему иметь возможность прикреплять к нему метаданные в форма атрибутов. Если я действительно владел объектом, я согласен с вами, что поля являются подходящим механизмом. Наконец, вы можете спросить: если я не владею объектом, почему я беспокоюсь о ссылках на атрибуты .self внутри объекта? Методы объектов ссылочного класса могут принимать функции в качестве аргументов (например, блоки в Ruby).   -  person Robert Krzyzanowski    schedule 01.04.2014
comment
и эти функции могут выполняться в среде объекта эталонного класса и, таким образом, иметь доступ к .self. Это не конкретная проблема, с которой я столкнулся, но она иллюстрирует теоретический вариант использования. В моем случае я создал подкласс объекта ссылочного класса и задался вопросом, могу ли я получить доступ к атрибутам на .self в методе для вышеупомянутых целей оптимизации, утверждая, что метаданные, прикрепленные к объекту, не были и не должны быть полем, потому что в нем закодирована информация о Совершенно другая мета-структура обертывания, но в конечном итоге я выбрал другой, более разумный подход.   -  person Robert Krzyzanowski    schedule 01.04.2014
comment
Я бы настоятельно не советовал прикреплять атрибуты к объекту со ссылочной семантикой. Неверно, что вы можете прикреплять атрибуты ко всем объектам R - не рекомендуется использовать атрибуты со средами и другими объектами со ссылочной семантикой.   -  person hadley    schedule 01.04.2014
comment
Понятно. Я не знал этого. Спасибо!   -  person Robert Krzyzanowski    schedule 01.04.2014


Ответы (1)


Думаю, я понял это. Я начал с того, что углубился в реализацию ссылочных классов для ссылок на .self.

 bodies <- Filter(function(x) !is.na(x),
   structure(sapply(ls(getNamespace('methods'), all.names = TRUE), function(x) {
     fn <- get(x, envir = getNamespace('methods'))
     if (is.function(fn)) paste(deparse(body(fn)), collapse = "\n") else NA
   }), .Names = ls(getNamespace('methods'), all.names = TRUE))
 )

Теперь bodies содержит именованный вектор символов всех функций в пакете methods. Теперь ищем .self:

goods <- bodies[grepl("\\.self", bodies)]
length(goods) # 4
names(goods) # [1] ".checkFieldsInMethod" ".initForEnvRefClass"  ".makeDefaultBinding"  ".shallowCopy"

Итак, в пакете methods есть четыре функции, содержащие строку .self. Их проверка показывает, что .initForEnvRefClass - это наш виновник. У нас есть заявление selfEnv$.self <- .Object. Но что такое selfEnv? Что ж, ранее в этой же функции у нас было [email protected] <- selfEnv. Действительно, глядя на атрибуты на нашем testInstance из примера, можно получить

$.xData
<environment: 0x10ae21470>

$class
[1] "testClass"
attr(,"package")
[1] ".GlobalEnv"

Заглядывание в attributes(attr(testInstance, '.xData')$.self) показывает, что мы действительно можем получить доступ к .self напрямую, используя этот подход. Обратите внимание, что после выполнения первых двух строк первого примера (т. Е. Настройки testInstance) мы имеем

identical(attributes(testInstance)$.xData$.self, testInstance)
# [1] TRUE

Да! Они равны. Теперь, если мы выполним

attr(testInstance, 'testAttribute') <- 1
identical(attributes(testInstance)$.xData$.self, testInstance)
# [1] FALSE

так что добавление атрибута к объекту ссылочного класса привело к созданию копии, и .self больше не идентичен объекту. Однако, если мы проверим, что

identical(attr(testInstance, '.xData'), attr(attr(testInstance, '.xData')$.self, '.xData'))
# [1] TRUE

мы видим, что окружение, связанное с объектом эталонного класса, остается прежним. Таким образом, копирование не имело большого значения с точки зрения занимаемой памяти.

Конечным результатом этого набега является то, что окончательный ответ - да, вам следует избегать установки атрибутов в ссылочных классах, если вы планируете использовать их в методах этого объекта. Причина этого в том, что объект .self в среде объекта ссылочного класса должен считаться фиксированным раз и навсегда после того, как объект был инициализирован, и это включает создание дополнительных атрибутов.

Поскольку объект .self хранится в среде, которая прикреплена как атрибут к объекту ссылочного класса, не представляется возможным избежать этой проблемы без использования йоги указателя - а R не имеет указателей.

Редактировать

Похоже, что если ты сумасшедший, ты можешь сделать

unlockBinding('.self', attr(testInstance, '.xData'))
attr(attr(testInstance, '.xData')$.self, 'testAttribute') <- 1
lockBinding('.self', attr(testInstance, '.xData'))

и вышеперечисленные проблемы волшебным образом исчезнут.

person Robert Krzyzanowski    schedule 31.03.2014