R中的引用调用(使用函数修改对象)

时间:2013-03-19 11:15:07

标签: r function

我只是在R中弄湿了,并且惊讶地看到一个函数没有修改对象,至少看起来这是默认值。例如,我写了一个函数只是为了在表中的一个标签上粘贴一个星号;它在函数内部工作,但表本身不会更改。 (我主要来自Ruby)

那么,使用函数来改变R中对象的正常,可接受的方式是什么?如何在表格标题中添加星号?

  • 替换整个对象:myTable = title.asterisk(myTable)

  • 使用变通方法通过引用进行调用(例如,在Call by reference in R中由TszKin Julian描述?

  • 使用函数以外的某些结构?对象方法?

3 个答案:

答案 0 :(得分:21)

您遇到问题的原因是您将对象传递到函数的本地命名空间。这是关于R的一个伟大/可怕的事情:它允许隐式变量声明,然后随着命名空间变得更深而实现超越。

这会影响您,因为函数会在当前命名空间中创建新的命名空间。我认为,对象'myTable'最初是在全局命名空间中创建的,但是当它传递给函数'title.asterisk'时,新的函数本地命名空间现在有一个具有相同属性的对象。这样工作原理如下:

title.asterisk <- function(myTable){ do some stuff to 'myTable' }

在这种情况下,函数'title.asterisk'不会对全局对象'myTable'进行任何更改。而是使用相同的名称创建本地对象,因此本地对象取代全局对象。如果我们以这种方式调用函数title.asterisk(myTable),则该函数仅对局部变量进行更改。

有两种直接的方法来修改全局对象(以及许多间接方式)。

选项1 :首先,正如您所提到的,第一个是让函数返回对象并覆盖全局对象,如下所示:

title.asterisk <- function(myTable){
    do some stuff to 'myTable'
    return(myTable)
}
myTable <- title.asterisk(myTable)

这没关系,但你仍然让你的代码有点难以理解,因为实际上有两个不同的'myTable'对象,一个是全局的,一个是函数的局部对象。许多程序员通过添加句号''来清除这一点。在变量参数前面,如:

title.asterisk <- function(.myTable){
    do some stuff to '.myTable'
    return(.myTable)
}
myTable <- title.asterisk(myTable)

好的,现在我们有一个视觉提示,两个变量是不同的。这很好,因为当我们稍后尝试调试我们的代码时,我们不想依赖名称空间超越等不可见的东西。它只会使事情变得更加艰难。

选项2 :您只需在函数中修改对象即可。当您想对对象进行破坏性编辑并且不希望内存膨胀时,这是更好的选择。如果您正在进行破坏性编辑,则无需保存原始副本。此外,如果您的对象适当大,您不希望在不需要时复制它。要编辑全局命名空间对象,只需将其传递给函数或从函数中声明它。

title.asterisk <- function(){ do some stuff to 'myTable' }

现在我们从函数中直接编辑对象'myTable'。我们没有传递对象的事实使我们的函数看起来更高级别的命名空间来尝试解析变量名称。瞧,它发现一个'myTable'对象更高了!函数中的代码对对象进行了更改。

要考虑的注意事项:我讨厌调试。我的意思是我真的讨厌调试。这对我来说意味着一些事情:

  • 我几乎把所有东西都包裹在一个函数中。当我编写代码时,只要我得到一个工作,我将它包装在一个函数中并将其放在一边。我大量使用'。'我的所有函数参数的前缀,并且对于它所存在的命名空间的本机内容,不使用任何前缀。
  • 我尽量不从函数中修改全局对象。我不喜欢这导致的地方。如果需要修改对象,我会从声明它的函数中修改它。这通常意味着我有多个函数调用函数,但它使我的工作既模块化又易于理解。
  • 我评论了我的所有代码,解释了每个行或块的用途。它可能看起来有点无关,但我发现这三件事对我来说都是一致的。一旦开始在函数中包装编码,您将发现自己想要重用更多的旧代码。这就是好评的地方。对我来说,这是必要的一部分。

答案 1 :(得分:9)

这两种范式正在替换整个对象,正如您所指示的,或者正在编写“替换”函数,例如

`updt<-` <- function(x, ..., value) {
    ## x is the object to be manipulated, value the object to be assigned
    x$lbl <- paste0(x$lbl, value)
    x
}

> d <- data.frame(x=1:5, lbl=letters[1:5])
> d
  x lbl
1 1   a
2 2   b
3 3   c
> updt(d) <- "*"
> d
  x lbl
1 1  a*
2 2  b*
3 3  c*

这是$<-的行为 - 就地更新$访问的元素。 Here是一个相关问题。人们可以将替换函数视为

的语法糖
updt1 <- function(x, ..., value) {
    x$lbl <- paste0(x$lbl, value)
    x
}
d <- updt1(d, value="*")

但是在我看来,标签“语法糖”对于所涉及的核心范式并没有真正公正。它支持方便的就地更新,这与R通常维护的更改时复制错觉不同,它实际上是更新对象的“R”方式(而不是使用?ReferenceClasses,例如,它有更多其他语言的感觉,但会让R用户期待复制更改语义。)

答案 2 :(得分:0)

对于以后寻求简化方法(不知道是否更合适)的任何人:

在函数内部创建对象以临时保存要更改的对象的修改版本。使用deparse(substitute())获取已传递给函数参数的变量的名称,然后使用assign()覆盖对象。您将需要在envir = parent.frame()内部使用assign(),以便在函数外部的环境中定义对象。

> (MyTable <- 1:10)
 [1]  1  2  3  4  5  6  7  8  9 10
>
> title.asterisk <- function(table) {
>   tmp.table <- paste0(table, "*")
>   name      <- deparse(substitute(table))
>   assign(name, tmp.table, envir = parent.frame())
> }
>
> (title.asterisk(MyTable))
 [1] "1*"  "2*"  "3*"  "4*"  "5*"  "6*"  "7*"  "8*"  "9*"  "10*"

在定义对象时使用括号比定义然后打印要有效(对我来说,更好看)。