【问题标题】:How does R handle object in function call?R如何处理函数调用中的对象?
【发布时间】:2011-09-24 05:37:17
【问题描述】:

我有 Java 和 Python 背景,最近在学习 R。

今天我发现 R 处理对象的方式似乎与 Java 和 Python 完全不同。

例如下面的代码:

x <- c(1:10)
print(x)
sapply(1:10,function(i){
            x[i] = 4
        })
print(x)

代码给出以下结果:

[1]  1  2  3  4  5  6  7  8  9 10
[1]  1  2  3  4  5  6  7  8  9 10

但我希望输出的第二行全是“4”,因为我在 sapply 函数中修改了向量。

那么这是否意味着 R 在函数调用中复制对象而不是引用对象?

【问题讨论】:

    标签: r object


    【解决方案1】:

    x 是在全局环境中定义的,而不是在您的函数中。

    如果您尝试在函数中修改非本地对象(例如 x),则 R 会复制该对象并修改该副本,因此每次运行匿名函数时都会创建 x 的副本,并且它的第 i 个组件设置为 4。当函数退出时,所创建的副本将永远消失。原x没有修改。

    如果我们要写x[i] &lt;&lt;- i 或者如果我们要写x[i] &lt;- 4; assign("x", x, .GlobalEnv),那么R 会写回它。写回它的另一种方法是将e 设置为存储x 的环境并执行以下操作:

    e <- environment()
    sapply(1:10, function(i) e$x[i] <- 4)
    

    或者可能是这样的:

    sapply(1:10, function(i, e) e$x[i] <- 4, e = environment())
    

    通常不会在 R 中编写这样的代码。而是将结果作为函数的输出生成,如下所示:

    x <- sapply(1:10, function(i) 4)
    

    (其实这种情况下可以写成x[] &lt;- 4。)

    添加:

    使用proto package 可以做到这一点,其中方法fx 属性的第i 个组件设置为4。

    library(proto)
    
    p <- proto(x = 1:10, f = function(., i) .$x[i] <- 4)
    
    for(i in seq_along(p$x)) p$f(i)
    p$x
    

    添加:

    在另一个选项上方添加了我们明确传递 x 存储的环境的选项。

    【讨论】:

    • 谢谢!但是为什么不在 R 中编写这样的代码呢?是否有一些潜在的风险或只是一个约定?我认为在其他语言的函数中修改全局对象是很正常的。
    • 在函数式语言中,函数不能有副作用。 R 并不那么严格,但 R 函数限制副作用仍然是正确的。最好按照预期使用的方式工作,而不是像用另一种语言一样尝试写作。有几个对象系统(S3、S4、参考类)。 S3是最常用的。 S4 要复杂得多。参考类是最近添加的。您可能希望特别探索参考类。还有一些用户贡献的包提供了不同的范例:proto 和 R.oo(可能还有其他)。
    • @Spirit Plus 你可以使用parent.frame(3) 而不是.GlobalEnv 将 x 存储在运行 sapply 的闭包中,这样会更安全。 (为什么是3?1-匿名功能框架,2-sapply框架,3-sapply外壳)
    【解决方案2】:

    是的,你是对的。检查 R 语言定义:4.3.3 Argument Evaluation

    AFAIK,在您尝试修改数据之前,R 不会真正复制数据,因此遵循 Copy-on-write 语义。

    【讨论】:

    • 谢谢阿纳托利!但是如果复制的数据真的很大,复制过程会占用太多时间和内存吗?或者它实际上并没有复制数据,只是在函数调用结束时中和了修改效果?
    • 有一个副本,但是函数内部的“x”和函数外部的不是同一个对象。有环境,在调用环境中有一个 x,在函数环境中有一个不同的 x。只有通过分配结果,更改才会在调用环境中可见。
    【解决方案3】:

    匿名函数内部的x不是全局环境(您的工作区)中的x。它是匿名函数本地的x 的副本。说R在函数调用中复制对象并不是那么简单;如果可以,R 将努力不复制,但一旦您修改了某些内容,R 就必须复制该对象。

    正如@DWin 指出的那样,x 的这个复制版本已修改sapply() 调用返回的,您声称的输出不是我得到的:

    > x <- c(1:10)
    > print(x)
     [1]  1  2  3  4  5  6  7  8  9 10
    > sapply(1:10,function(i){
    +             x[i] = 4
    +         })
     [1] 4 4 4 4 4 4 4 4 4 4
    > print(x)
     [1]  1  2  3  4  5  6  7  8  9 10
    

    很明显,该代码几乎完成了您的预期。问题是来自sapply() 的输出没有分配给对象,因此被打印并被丢弃。

    您编写代码甚至可以工作的原因是由于 R 的范围规则。您确实应该将函数需要的任何对象作为参数传递给函数。但是,如果 R 无法找到该函数的本地对象,它将在父环境中搜索与名称匹配的对象,然后在适当的情况下搜索该环境的父环境,最终到达全局环境,即工作空间。因此,您的代码可以正常工作,因为它最终找到了可使用的 x,但立即被复制,该副本在 sapply() 调用结束时返回。

    在许多情况下,这种复制确实需要时间和内存。这是人们认为for 循环在 R 中运行缓慢的原因之一;他们在用循环填充对象之前不会为对象分配存储空间。如果不分配存储,R 必须修改/复制对象以添加循环的下一个结果。

    尽管如此,R 中的任何地方并不总是那么简单,例如在环境中,环境的副本实际上只是指原始版本:

    > a <- new.env()
    > a
    <environment: 0x1af2ee0>
    > b <- 4
    > assign("b", b, env = a)
    > a$b
    [1] 4
    > c <- a ## copy the environment to `c`
    > assign("b", 7, env = c) ## assign something to `b` in env `c`
    > c$b ## as expected
    [1] 7
    > a$b ## also changed `b` in `a` as `a` and `c` are actually the same thing
    [1] 7
    

    如果您了解这些内容,请阅读 R Language Definition 手册,该手册涵盖了 R 引擎盖下的许多细节。

    【讨论】:

    • 糟糕 - 我是在几个小时前写的(英国时间早上),但一定是跑题了,没有点击提交按钮。
    【解决方案4】:

    您需要将 sapply 的输出分配给一个对象,否则它就会消失。 (实际上你可以恢复它,因为它也被分配给.Last.value

    x <- c(1:10)
    print(x)
     [1]  1  2  3  4  5  6  7  8  9 10
    x <- sapply(1:10,function(i){
                 x[i] = 4
            })
    print(x)
     [1] 4 4 4 4 4 4 4 4 4 4
    

    【讨论】:

    • 对不起,但这并不能回答问题,只是令人困惑。
    • 现在我很困惑。 OP 创建了一个 4 的向量,然后什么也没做。如果他想让“x”改变,他需要使用赋值操作。我以为我准确地回答了这个问题。
    • 您似乎建议 v&lt;-sapply(...,function(...){...v...}) 构造以某种方式将 v 从函数环境导出到父环境。更不用说这个问题非常直接地是关于 R 是进行复制调用还是引用调用。
    • 是的,我 am 建议 v .Last.value 导出到 globalenv() 并且分配函数 &lt;- 是“让它粘住。”形式上讲它是“pass-by-promise”,但实际上它更像是“pass-by-value”。它不是“出口 x”,我也没有说它是。它正在导出值,&lt;- 以持久的方式命名它们。
    • 但是= 只是返回4,而不是x[i] 的承诺;这样就相当于x&lt;-sapply(1:10,function(ignoreIt) 4)
    【解决方案5】:

    如果您想从函数中更改“全局”对象,则可以使用非本地赋值。

    x <- c(1:10)
    # [1]  1  2  3  4  5  6  7  8  9 10
    print(x)
    sapply(1:10,function(i){
                x[i] <<- 4
            })
    print(x)
    # [1] 4 4 4 4 4 4 4 4 4 4
    

    尽管在这种特殊情况下,您可以更紧凑地使用 x[]&lt;-4

    顺便说一句,这是 R 的一个不错的特性——而不是 sapply(1:10,function(i) x[i] &lt;&lt;- 4for(i in 1:10) x[i]&lt;-4for 不是一个函数,所以这里不需要 &lt;&lt;-)你可以写x[]&lt;-4 :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多