通过引用传递函数时处理 R data.table 中的无效 selfref
Posted
技术标签:
【中文标题】通过引用传递函数时处理 R data.table 中的无效 selfref【英文标题】:Handling invalid selfref in R data.table when passing by reference to a function 【发布时间】:2014-09-07 16:10:04 【问题描述】:我的小组使用 data.table 编写了很多代码,我们偶尔会被“检测到无效 .internal.selfref 并通过获取整个表的副本来修复...”警告所困扰。当通过引用函数传递数据表时,这种行为可能会破坏我们的代码,而我正试图弄清楚如何解决它。
假设我有一个函数可以将一列添加到 data.table 作为副作用——注意原始 data.table 没有返回。
foo <- function(mydt)
mydt[, c := c("a", "b")]
return(123)
)
> x<- data.table(a=c(1,2), b=c(3,4))
> foo(x)
[1] 123
> x
a b c
1: 1 3 a
2: 2 4 b
x 已更新为新列。这是期望的行为。
现在假设发生了一些事情,破坏了 x 中的内部自引用:
> x<- data.table(a=c(1,2), b=c(3,4))
> x[["a"]] <- c(7,8)
> foo(x)
[1] 123
Warning message:
In `[.data.table`(mydt, , `:=`(c, c("a", "b"))) :
Invalid .internal.selfref detected and fixed by taking a copy ...
> x
a b
1: 7 3
2: 8 4
我了解(大部分)发生了什么。 [["a"]] 构造对 data.table 不友好; x 被转换为数据框,然后返回到数据表,这不知何故弄乱了内部工作。然后在foo()里面,在添加列的引用操作过程中,检测到了这个问题,复制了一份mydt;新列“c”已添加到 mydt。但是,该复制操作切断了 x 和 mydt 之间的按引用传递关系,因此附加列不是 x 的一部分。
函数 foo() 将被不同的人使用,并且很难防止无效的内部 selfref 情况。那里的人可能很容易做类似 x[["a"]] 的事情,这会导致输入无效。我试图弄清楚如何从 foo 内部处理这个问题。
到目前为止我有这个想法,在foo()的开头:
if(!data.table:::selfrefok(mydt)) stop("mydt is corrupt.")
这至少让我们有机会发现问题,但它对 foo() 的用户不是很友好,因为这些输入被破坏的方式可能非常不透明。理想情况下,我希望能够纠正损坏的输入并保持 foo() 的所需功能。但是我看不到如何,除非我重组我的代码以便 foo 返回 mydt 并将其分配给调用范围内的 x ,这是可能的,但并不理想。有什么想法吗?
【问题讨论】:
【参考方案1】:你应该阅读整个警告......
然后你会注意到
在更早的时候,这个 data.table 已经被 R 复制了(或者是使用 structure() 或类似方法手动创建的)。避免 key
[[<-
与names<-
和attr<-
相似,因为它会创建一个副本。
可以确保按引用的行为是用substitute构造调用,然后在父框架中求值
foo <- function(x)
l <- substitute(x[,c := 'a'], as.list(match.call())['x']);
eval.parent(l)
return(123)
xx<- data.table(a=c(1,2), b=c(3,4))
xx[["a"]] <- c(7,8)
foo(xx)
# [1] 123
# Warning message: .....
# but it now works!
xx
# a b c
# 1: 7 3 a
# 2: 8 4 a
警告仍然存在,但功能按预期工作。
【讨论】:
是的,我知道它会创建一个副本。挑战在于我不能保证我的代码的用户不会使用 [[ 【参考方案2】:@pteehan,好问题!在我看来,更简洁的解决方法是在分配步骤本身期间恢复过度分配,并带有 警告 基本上说 “不要这样做! ”。
这样做的方法是通过[[<-.data.table
方法,目前不存在。除非我遗漏了什么,否则它会是一个很好的补充,其目的不是鼓励使用它,而是为了抓住这样的案例并引导人们正确使用(带有警告),同时恢复超额分配。
大致:
`[[<-.data.table` <- function(x, i, j, value)
warning("Don't do this. Use := instead.")
call = sys.call()
call[[1L]] = `[[<-.data.frame`
ans = copy(eval(call, envir=parent.frame()))
foo <- function(mydt)
mydt[, c := c("a", "b")]
return(123)
x <- data.table(a = c(1,2), b = c(3,4))
x[["a"]] <- c(7,8)
# Warning message:
# In `[[<-.data.table`(`*tmp*`, "a", value = c(7, 8)) :
# Don't do this. Use := instead.
data.table:::selfrefok(x)
# [1] 1
foo(x)
# [1] 123
x
# a b c
# 1: 7 3 a
# 2: 8 4 b
我相信这些方面的东西应该提供一个更清洁的解决方案。也许这应该得到实施。
PS:This post 详细解释了您问题中出现警告的原因。
【讨论】:
以上是关于通过引用传递函数时处理 R data.table 中的无效 selfref的主要内容,如果未能解决你的问题,请参考以下文章
使用带有R内核的jupyter笔记本,如何通过引用来抑制打印结果更新data.table?
data.table 包中的 := (按引用传递)运算符同时修改另一个数据表对象