gpt4 book ai didi

r - 为什么 R 在引用类对象上使用属性时反复无常?

转载 作者:行者123 更新时间:2023-12-01 13:43:19 25 4
gpt4 key购买 nike

我在实现访问附加到引用类对象的属性的一致行为时遇到了一些麻烦。例如,

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解释说

The entire object can be referred to in a method by the reserved name .self ... These fields are read-only (it makes no sense to modify these references), with one exception. In principal, the .self field can be modified in the $initialize method, because the object is still being created at this stage.

我对这一切都很满意,并同意作者的决定。但是,我担心的是以下内容。回到上面的第一个例子,如果我们尝试请求 attr(testInstance, 'testAttribute') ,我们从全局环境看是1 !

据推测,.self在引用类对象的方法中使用的存储在与 testInstance 相同的内存位置。 ——是同一个对象。因此,通过在 testInstance 上设置属性在全局环境中成功,但不是 .self引用(如第一个示例所示),我们是否无意中触发了全局环境中整个对象的副本?或者属性的存储方式是否“有趣”,即对象可以驻留在同一内存中,但其属性因调用环境而异?

我看不到其他解释为什么 attr(.self, 'testAttribute')NULL但是attr(testInstance, 'testAttribute')1 . 绑定(bind) .self被一劳永逸地锁定,但这并不意味着它引用的对象不能改变。如果这是所需的行为,这似乎是一个问题。

最后一个问题是前面的结果是否暗示 attr<-应该避免在引用类对象上使用,至少如果从对象的方法中使用结果属性。

最佳答案

我想我可能已经弄明白了。我首先深入研究引用类的实现以引用 .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 is our culprit .我们有声明 selfEnv$.self <- .Object .但什么是 selfEnv ?好吧,在同一个函数的前面,我们有 .Object@.xData <- 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对象存储在作为属性附加到引用类对象的环境中,如果不使用指针 yoga 似乎无法避免这个问题——而且 R 没有指针。

编辑

看来,只要你疯了,你就可以做到

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

上述问题神奇地消失了。

关于r - 为什么 R 在引用类对象上使用属性时反复无常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22752021/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com