首页 > 解决方案 > 锁定 data.table 表的内容

问题描述

是否可以将 data.table 设为静态(即不可更新)?使用 lockBinding() 函数可以防止重新分配变量,但是仍然可以编辑数据表的列。例子:

> dt = data.table( x = 1:5 )
> lockBinding( "dt", env = environment() )
> dt = 1
Error: cannot change value of locked binding for 'dt'
> dt[ , x := 1 ]
> dt[ , x ]
[1] 1 1 1 1 1

我想这与如何引用数据表有关,但是,能够锁定数据表的内容也会很有用。(我经常有不想意外更新的共享参考表。)

标签: rdata.table

解决方案


这有点棘手。一种方法是劫持[函数以禁止在:=对象上使用。如果我们想绑定一个 data.table,我们可以给它添加一个类,像这样:

boundDT <- function(dt){
  class(dt) <- c("bound.data.table", class(dt))
  dt
}

结果:

library(data.table)
dt = data.table( x = 1:5 )
bound <- boundDT(dt)
class(bound)
[1] "bound.data.table" "data.table"       "data.frame"   

如果我们然后创建一个新的索引函数来处理这个bound.data.table类,我们可以做我们的事情:

`[.bound.data.table` <- function(dt, ...){
  if(any(unlist(sapply(match.call()[-(1:2)], function(x) if(length(x) > 1)as.character(x[1]) == ":=")))){
    stop("Can't use `:=` on this object.")
  }
  class(dt) <- class(dt)[-1]
  dt[...]
}

这将检查该函数是否在:=调用中使用,如果使用则抛出错误。否则它会删除 data.table 内部副本上的绑定类,并调用常规[函数。

bound[, x := 1]
 Error in `[.bound.data.table`(bound, , `:=`(x, 1)) : 
  Can't use `:=` on this object. 
bound[, x]
[1] 1 2 3 4 5

这很丑陋,但似乎有效。

一个警告:

在连接中使用:=时,如果绑定表不是基表,则此方法不起作用:

dt = data.table( x = 1:5 , y = 5:1)
bound <- boundDT(dt)
dt[bound, y := 1, on = .(x = x)]
bound
   x y
1: 1 1
2: 2 1
3: 3 1
4: 4 1
5: 5 1

然而:

bound[dt, y := 1, on = .(x = x)]
 Error in `[.bound.data.table`(bound, dt, `:=`(y, 1), on = .(x = x)) : 
  Can't use `:=` on this object.

防止使用set*

解决了运算符周围的大多数问题:=,我们可以专注于防止set*在我们的对象上使用。

set*当使用绑定的data.table时,我们可以在提供data.table之前检查调用堆栈是否有任何函数。

bindDT <- function(dt){
  bound <- boundDT(dt)
  function(){
    calls <- sys.calls()
    forbidden <- c("set", "set2key", "set2keyv", "setattr", "setcolorder", "setdiff", "setDT", 
                   "setDTthreads", "setequal", "setindex", "setindexv", "setkey", "setkeyv", 
                   "setnames", "setNumericRounding", "setorder", "setorderv")
    matches <- unlist(lapply(calls, function(x) as.character(x)[1] %in% forbidden))
    if(any(matches)){
      stop(paste0("Can't use function ", calls[[which(matches)[1]]][1], " on bound data.table."))
    }

    bound
  }
}

此函数像以前一样绑定 data.table,但它不是返回 this,而是返回一个函数。此函数在调用时会检查调用堆栈中的set*函数,如果找到则抛出错误。我从 data.table 帮助页面得到了这个列表,所以这应该是完整的。

您可以使用主动绑定来避免每次使用都必须调用 data.table 作为函数,使用pryr

library(data.table)
library(pryr)

dt = data.table( x = 1:5 , y = 5:1)
bound %<a-% (bindDT(dt))()

setkey(bound, x)
Error in (bindDT(dt))() : Can't use function setkey on bound data.table.

推荐阅读