在R中,你能否通过引用传递参数?

74

你能使用 "R" 通过引用传递吗?例如,在以下代码中:

setClass("MyClass",
    representation(
    name="character"
    ))


instance1 <-new("MyClass",name="Hello1")
instance2 <-new("MyClass",name="Hello2")

array = c(instance1,instance2)

instance1
array

instance1@name="World!"

instance1
array

输出结果为

> instance1
An object of class “MyClass”
Slot "name":
[1] "World!"

> array
[[1]]
An object of class “MyClass”
Slot "name":
[1] "Hello1"


[[2]]
An object of class “MyClass”
Slot "name":
[1] "Hello2"

但我希望它是这样的

> instance1
An object of class “MyClass”
Slot "name":
[1] "World!"

> array
[[1]]
An object of class “MyClass”
Slot "name":
[1] "World!"


[[2]]
An object of class “MyClass”
Slot "name":
[1] "Hello2"

这是否有可能?


2
我真的很好奇为什么他们会想出这样一个不寻常的实现方式。 - anilbey
6
对象或基元?S3、S4或R6?使用环境还是其他方式?R 1.x、2.x还是3.x?这里的答案跨越了2010-15年,它们彼此不一致。这个问题非常混乱,需要整理。另外,在回答“是/否”时,最好引用版本或日期,例如“截至R 3.0 / 2013”。这样可以让答案具有面向未来的效应。 - smci
9个回答

55

不可以.

在赋值语句中,对象是不可变的。R会复制对象,而不仅仅是引用。

> v = matrix(1:12, nrow=4)
> v
           [,1] [,2] [,3]
     [1,]    1    5    9
     [2,]    2    6   10
     [3,]    3    7   11
     [4,]    4    8   12
> v1 = v
> v1[,1]     # fetch the first column 
     [1] 1 2 3 4

(须知: 上述声明仅适用于R语言的基元,例如向量、矩阵,以及函数;我无法确定它是否适用于所有的R对象——大多数对象都适用,尤其是最常使用的对象。)

如果您不喜欢这种行为,可以通过使用R包来选择退出。例如,有一个名为R.oo的R包,允许您模拟按引用传递的行为; R.oo可在CRAN上获取。


5
也请参考 mutatrproto 包。 - hadley
"mutatr" 似乎不受支持且没有文档说明。 - krlmlr
@doug,你能否使用C++的.Call包装器通过引用传递吗? - nopeva
19
我发现这个 "No." 相当,嗯,大胆,因为许多软件包似乎允许通过引用传递参数,以及 Rcpp 与 C/C++ 的接口。 - Hugo Raguet

52

请注意,如果您希望使用按引用传递仅仅为了避免复制一个不被修改的对象所带来的性能影响(这在其他使用常量引用的语言中很常见),R 会自动做到这一点:

n <- 10^7
bigdf <- data.frame( x=runif(n), y=rnorm(n), z=rt(n,5) )
myfunc <- function(dat) invisible(with( dat, x^2+mean(y)+sqrt(exp(z)) ))
myfunc2 <- function(dat) {
    x <- with( dat, x^2+mean(y)+sqrt(exp(z)) )
    invisible(x)
}
myfunc3 <- function(dat) {
    dat[1,1] <- 0
    invisible( with( dat, x^2+mean(y)+sqrt(exp(z)) ) )
}
tracemem(bigdf)
> myfunc(bigdf)
> # nothing copied
> myfunc2(bigdf)
> # nothing copied!
> myfunc3(bigdf)
tracemem[0x6e430228 -> 0x6b75fca0]: myfunc3 
tracemem[0x6b75fca0 -> 0x6e4306f0]: [<-.data.frame [<- myfunc3 
tracemem[0x6e4306f0 -> 0x6e4304f8]: [<-.data.frame [<- myfunc3 
> 
> library(microbenchmark)
> microbenchmark(myfunc(bigdf), myfunc2(bigdf), myfunc3(bigdf), times=5)
Unit: milliseconds
            expr       min        lq    median        uq       max
1 myfunc2(bigdf)  617.8176  641.7673  644.3764  683.6099  698.1078
2 myfunc3(bigdf) 1052.1128 1134.0822 1196.2832 1202.5492 1206.5925
3  myfunc(bigdf)  598.9407  622.9457  627.9598  642.2727  654.8786

8
很有帮助的信息!我还要补充说,这似乎仅适用于数据框(data.frame)。正如我在查看 Rprof 输出几个小时后刚刚了解到的那样,矩阵/数组始终是按值传递的。 - Andrew Christianson
2
现在我正在我的笔记本电脑上重新运行这个程序:所有时间现在都是相同的(并且是五年前的一半)。 - user189035
tracemem 部分需要进一步解释。 - cloudscomputes
@AndrewChristianson 如果我理解正确的话,这是当前的行为,也适用于“matrix”,“array”和“tibble”。自您发表评论以来,行为是否已更改,或者我错了? - Oren Milman
@OrenMilman 哦天啊,可能吗?那个评论是几年前的事了,可能是针对我当时使用的 R 2.15 / 2.14 发表的。 - Andrew Christianson

30

正如之前一些人指出的那样,可以通过使用environment类的对象来实现此操作。存在一种基于environment使用的正式方法,它被称为参考类,可以让事情变得非常简单。检查?setRefClass以获取主要入口帮助页面。它还描述了如何在参考类中使用正式方法。

示例

setRefClass("MyClass",
    fields=list(
        name="character"
    )
)

instance1 <- new("MyClass",name="Hello1")
instance2 <- new("MyClass",name="Hello2")

array = c(instance1,instance2)

instance1$name <- "World!"

输出

> instance1
Reference class object of class "MyClass"
Field "name":
[1] "World!"

> array
[[1]]
Reference class object of class "MyClass"
Field "name":
[1] "World!"

[[2]]
Reference class object of class "MyClass"
Field "name":
[1] "Hello2"

26

可以通过引用传递来处理environment,使用这种方法时,每当您创建一个对象时,您需要同时创建一个环境插槽。但我认为这很繁琐。请参考S4的传递方式指针和在R中传递引用


链接现在可用。这些天应该将S4列在S3之前。 - smci

6

R现在有一个库,可以使用引用实现面向对象编程。请参见ReferenceClasses,它是methods包的一部分。


4
实际上,R.oo 包通过使用环境来模拟按引用传递的行为。

3

正如其他人所说,S4类不支持此功能。但R现在提供了使用R6库的可能性,称为reference类。请参阅官方文档


1
R6是一个由用户贡献的软件包。引用类是R(或者说是它的方法软件包)自带的。正如文档所述,R6与引用类相似:"R6类类似于R标准引用类"。 - Helix123

2
除了其他已经回答过的通过引用(环境对象和引用类)传递对象的答案,如果您只是出于语法便利而对按引用调用感兴趣(即,您不介意在内部复制数据),则可以通过将最终值分配回外部变量并返回来模拟该过程。
byRef <- function(..., envir=parent.frame(), inherits=TRUE) {
  cl <- match.call(expand.dots = TRUE)
  cl[c(1, match(c("envir", "inherits"), names(cl), 0L))] <- NULL
  for (x in as.list(cl)) {
    s <- substitute(x)
    sx <- do.call(substitute, list(s), envir=envir)
    dx <- deparse(sx)
    expr <- substitute(assign(dx, s, envir=parent.frame(), inherits=inherits))
    do.call(on.exit, list(expr, add=TRUE), envir=envir)
  }
}

然后我们可以声明“按引用传递”的参数:
f <- function(z1, z2, z3) {
  byRef(z1, z3)

  z1 <- z1 + 1
  z2 <- z2 + 2
  z3 <- z3 + 3

  c(z1, z2, z3)
}

x1 <- 10
x2 <- 20
x3 <- 30

# Values inside:
print(f(x1, x2, x3))
# [1] 11 22 33

# Values outside:
print(c(x1, x2, x3))
# [1] 11 20 33

请注意,如果您在函数内部通过外部名称(x1x3)访问“按引用”变量,则无论何时,都将从外部获取它们尚未修改的值。另外,此实现仅处理简单的变量名作为参数,因此索引参数(如f(x[1], ...))将不起作用(虽然您可能可以通过更复杂的表达式操作来绕过有限的assign来实现该目的)。

2
除了其他建议,您还可以编写以引用方式接受参数并在原地工作的C/C++函数,并借助于Rcpp(等)直接在R中调用它们。特别是请参阅this answer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接