【问题标题】:Can a function inside a list access the list's attributes in R?列表中的函数可以访问 R 中列表的属性吗?
【发布时间】:2020-11-07 11:49:19
【问题描述】:

在以下示例中,列表中的函数应返回字符串 "The value currently is 42"

l <- list(
  param = 42,
  f = function() { paste("The value currently is", param) }
)

(受 Mozilla 关键字 this 的 js 文档启发的示例)

由于object 'param' not found,显然调用l$f() 失败。

我尝试使用ls.str()ls() 检查环境中可用的对象。我找到的最接近的解决方案是引用分配给列表的变量名称。

l <- list(
  param = 42,
  f = function() { paste("The value currently is", l$param) }
)

这当然不是最理想的,因为变量可以命名为任何名称。

l 内部的函数有没有办法引用它的相邻属性param?如果没有,是否有一些嵌套的更复杂的解决方案可以完成类似的任务?

【问题讨论】:

  • 您是否希望将其应用于特定的用例?这个结构对我来说看起来很奇怪,我真的无法想象它的用途是什么。也许一个例子可以帮助我弄清楚什么样的结构可以实现你的目标。
  • @Julian_Hn 有点难以描述,但list 中有十几个属性我想在列表中有一个函数,它根据属性的值返回一些有用的信息(例如构建列表的一些字符串表示,获取所有负数的数字属性等)。我不想为此创建传递列表的全局函数。我在 JavaScript 中使用了很多 this 方法,尽管我认为在 R 中对于此类问题还有其他范例。
  • 这不是 OOP 的典型例子吗?因此,创建一个具有分配函数的类,然后您可以将这些函数应用于该类的每个实例,并对这个对象执行所需的操作。

标签: r list function reference


【解决方案1】:

好的,所以我想出的一个解决方案是让一个函数创建列表对象。这样我就知道变量名是什么了,尽管从我有限的测试中我不能确定没有任何隐藏的错误。

createList <- function() {
  l <- list(
    param = 42,
    f = function() { paste("The value currently is", l$param) }
  )

  return(l)
}

# name shadowing doesn't seem to be an issue
l <- createList()

# prints "The value currently is 42"
print(l$f())

编辑:当 param 被修改时,这不起作用。

l <- createList()
l$f() # 42

l$param <- 0
l$f() # 42

编辑:与@Darren 的解决方案一样,可以使用assign 在函数内部重新定义该值。

l <- createList()

# param has to be updated at two locations
l$param <- 0
assign("param", 0, envir = environment(l$f))

此时不妨开始使用类。

【讨论】:

    【解决方案2】:

    tibble 包中的lst() 按顺序构建组件。定义组件时,可以参考调用之前创建的组件。

    l <- tibble::lst(
      param = 42,
      f = function() { paste("The value currently is", param) }
    )
    
    l$f()
    
    # [1] "The value currently is 42"
    

    f 中的param 值不能简单地替换为l$param &lt;- value,因为它已存储到与f 关联的环境中。您可以使用 assign() 并为该环境指定 envir 参数并覆盖它。

    assign("param", 10, envir = environment(l$f))
    
    l$f()
    
    # [1] "The value currently is 10"
    

    【讨论】:

    • 这太棒了,谢谢!虽然可能没有办法更新param 的值并且函数f 也使用更新的值,对吧?
    • @FelixJassler 可以用assign()更新。查看我的编辑。
    【解决方案3】:

    您可以在函数内部使用match.call 从用于访问函数的子集调用中捕获列表的名称。如果你在父框架中 eval 这个符号,你有一个(取消引用的)this 的等价物。

    这给出了所需的行为,如以下表示所示:

    l <- list(a = 1, 
              f = function() {
                mc <- match.call()
                this_sym <- as.list(as.list(mc)[[1]])[[2]]
                this <- eval(this_sym, envir =  parent.frame())
                cat("Value of ", as.character(this_sym), "$a is ", this$a, sep ="")
              })
    
    l$f()
    #> Value of l$a is 1
    
    l$a <- 42
    
    l$f()
    #> Value of l$a is 42
    
    new_list <- l
    
    new_list$f()
    #> Value of new_list$a is 42
    

    但是,这感觉就像是在尝试使用普通列表来复制那种已经可以通过 R6 或 S4 类做得更好的面向对象编程。

    reprex package (v0.3.0) 于 2020 年 7 月 17 日创建

    【讨论】:

      【解决方案4】:

      如果不是构建一个列表,而是构建一个原型对象,它可以很容易地完成。 (原型对象是具有附加方法的环境。)

      library(proto)
      
      p <- proto(param = 42, f = function(.) paste("The current value is", .$param))
      p$f()
      ## [1] "The current value is 42"
      

      稍后更改参数使用:

      p$param <- 43
      

      或创建一个不修改 p 的子对象,而是创建一个新对象,该对象在具有自己的 param 值的新对象 p2 中重用其父对象的 f 方法。

      p2 <- p$proto(param = 44)
      
      p2$f()
      ## [1] "The current value is 44"
      

      以上是执行此操作的首选方法,但如果您不打算从 p 生成子对象,那么这也可以。除了我们使用 param 而不是 .$param 之外,它是相同的。

      p3 <- proto(param = 42, f = function(.) paste("The current value is", param))
      p3$f()
      ## [1] "The current value is 42"
      

      【讨论】:

        猜你喜欢
        • 2011-04-19
        • 2011-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-13
        • 2018-01-17
        • 2015-08-19
        • 1970-01-01
        相关资源
        最近更新 更多