了解自定义就地修改功能的代码吗?

时间:2018-08-09 14:34:04

标签: r data.table

我碰到了这篇帖子:http://r.789695.n4.nabble.com/speeding-up-perception-tp3640920p3646694.html,来自Matt Dowle,讨论得还早? data.table包的实现思路。

他使用以下代码:

x = list(a = 1:10000, b = 1:10000) 
class(x) = "newclass" 
"[<-.newclass" = function(x,i,j,value) x      # i.e. do nothing 
tracemem(x)
x[1, 2] = 42L 

具体地说,我正在查看:

"[<-.newclass" = function(x,i,j,value) x

我试图了解在此所做的工作以及如何使用此表示法。

在我看来,是这样:

  • i是行索引
  • j是列索引
  • 值是要分配的值
  • x是正在考虑的对象

因此,我最好的猜测是我为就位修改(对于给定的类)定义了一个自定义函数。

[<-.newclass在类newclass的类修改中。

了解发生了什么: 通常,以下代码应返回错误:

x = list(a = 1:10000, b = 1:10000) 
x[1, 2] = 42L 

所以我想示例代码没有任何实际用途。

尝试使用以下逻辑:

一个简单的无意义的尝试是将要插入的值平方:

x[i, j] <- value^2

完全尝试:

> x = matrix(1:9, 3, 3)
> class(x) = "newclass"
> "[<-.newclass" = function(x, i, j, value) x[i, j] <- value^2 # i.e. do something
> x[1, 2] = 9
Error: C stack usage  19923536 is too close to the limit

这似乎不起作用。

我的问题

"[<-.newclass" = function(x,i,j,value) x 

此符号的工作原理以及我将如何使用它?

(我想添加data.table标记,因为链接的讨论是关于data.table中“按引用”的就地修改)。

1 个答案:

答案 0 :(得分:18)

`[<-`()函数(传统上)用于subassignment,并且更广泛地是replacement function的一种。它也是通用的(更具体地说,是internal generic),它允许您正确猜想后write custom methods

替换功能

通常,当您调用替换功能时,例如...

foo(x) <- bar(y)

... <-右侧的表达式(所以这里是bar(y))被作为命名为value的参数传递给`foo<-`(),{{1} }作为第一个参数,并向对象x重新分配结果:也就是说,上述调用等效于编写:

x

因此,为了完全起作用,所有替换函数必须至少包含两个参数,其中之一必须命名为x <- `foo<-`(x, value = bar(y)) 。 大多数替换函数只有两个自变量,但也有例外:例如`attr<-`,通常是 subassignment

子分配

当您进行诸如value之类的子分配调用时,x[i, j] <- yi作为附加参数通过j和{{1 }}分别作为第一个参数和`[<-`()

x

对于yvaluex <- `[<-`(x, i, j, value = y) # x[i, j] <- y matrix将用于选择行和列;但通常情况并非必须如此。自定义类的方法可以对参数执行任何操作。考虑以下示例:

data.frame

这有用还是合理? 可能不是。但是它是有效的R代码吗? 绝对!

在实际的应用程序中看到自定义子分配方法的情况较少见,因为i通常基于类的基础对象,可以按您期望的那样“正常工作”。 `[<-.data.frame`是一个值得注意的例外,其中基础对象是列表,但是子分配的行为类似于矩阵。 (另一方面,许多类 do 需要一个自定义sub setting 方法,因为默认的j方法会删除大多数属性,包括x <- matrix(1:9, 3, 3) class(x) <- "newclass" `[<-.newclass` <- function(x, y, z, value) { x + (y - z) * value # absolute nonsense } x[1, 2] <- 9 x #> [,1] [,2] [,3] #> [1,] -8 -5 -2 #> [2,] -7 -4 -1 #> [3,] -6 -3 0 #> attr(,"class") #> [1] "newclass" 属性,有关详细信息,请参见`[<-`()


关于您的示例为何不起作用的原因:请记住,您正在为通用函数编写方法,并且所有常规规则都适用。如果我们使用`[`()的函数形式并扩展示例中的方法dispatch,我们可以立即看到失败的原因:

class

也就是说,该函数是在没有基本情况的情况下递归定义的,从而导致无限循环。解决此问题的一种方法是在调用下一个方法之前先进入?`[`

`[<-`()

(或者,使用某种更高级的技术,也可以将主体替换为诸如`[<-.newclass` <- function(x, i, j, value) { x <- `[<-.newclass`(x, i, j, value = value^2) # x[i, j] <- value^2 } 的显式next方法。这种方法在继承和超类方面表现更好。)

只是为了验证它是否有效:

unclass(x)

完全混乱!


对于Dowle的“不做任何事情”子分配示例的上下文,我相信这是为了说明在R 2.13.0中,自定义子分配方法将始终导致制作对象的深层副本,即使方法本身什么也没做。 (我相信自R 3.1.0起,情况已不再如此。)

reprex package(v0.2.0)于2018-08-15创建。