【问题标题】:How to use R's ellipsis feature when writing your own function?编写自己的函数时如何使用 R 的省略号功能?
【发布时间】:2011-03-04 16:40:47
【问题描述】:

R 语言有一个很好的功能,可以定义可以接受可变数量参数的函数。例如,函数data.frame 接受任意数量的参数,每个参数都成为结果数据表中一列的数据。示例用法:

> data.frame(letters=c("a", "b", "c"), numbers=c(1,2,3), notes=c("do", "re", "mi"))
  letters numbers notes
1       a       1    do
2       b       2    re
3       c       3    mi

函数的签名包括一个省略号,如下所示:

function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE, 
    stringsAsFactors = default.stringsAsFactors()) 
{
    [FUNCTION DEFINITION HERE]
}

我想编写一个函数来做类似的事情,获取多个值并将它们合并为一个返回值(以及进行一些其他处理)。为了做到这一点,我需要弄清楚如何从函数内的函数参数中“解包”...。我不知道该怎么做。 data.frame的函数定义中的相关行是object <- as.list(substitute(list(...)))[-1L],我看不懂。

那么如何将函数签名中的省略号转换为例如列表?

更具体地说,我如何在下面的代码中写get_list_from_ellipsis

my_ellipsis_function(...) {
    input_list <- get_list_from_ellipsis(...)
    output_list <- lapply(X=input_list, FUN=do_something_interesting)
    return(output_list)
}

my_ellipsis_function(a=1:10,b=11:20,c=21:30)

编辑

似乎有两种可能的方法来做到这一点。他们是as.list(substitute(list(...)))[-1L]list(...)。但是,这两个做的事情并不完全相同。 (有关差异,请参阅答案中的示例。)谁能告诉我它们之间的实际区别是什么,我应该使用哪一个?

【问题讨论】:

    标签: r function parameters ellipsis variadic


    【解决方案1】:

    我阅读了答案和 cmets,发现有几件事没有被提及:

    1. data.frame 使用list(...) 版本。代码片段:

      object <- as.list(substitute(list(...)))[-1L]
      mrn <- is.null(row.names)
      x <- list(...)
      

      object 用于对列名做一些魔术,但x 用于创建最终的data.frame
      要使用未评估的... 参数,请查看使用match.callwrite.csv 代码。

    2. 正如您在 Dirk 的评论结果中所写,答案不是列表列表。是一个长度为 4 的列表,其中元素是 language 类型。第一个对象是symbol - list,第二个是表达式1:10,依此类推。这就解释了为什么需要[-1L]:它从... 中提供的参数中删除了预期的symbol(因为它始终是一个列表)。
      正如 Dirk 所说,substitute 返回“解析树未计算的表达式”。
      当您调用 my_ellipsis_function(a=1:10,b=11:20,c=21:30) 时,... “创建”参数列表:list(a=1:10,b=11:20,c=21:30)substitute 使其成为四个元素的列表:

      List of 4
      $  : symbol list
      $ a: language 1:10
      $ b: language 11:20
      $ c: language 21:30
      

      第一个元素没有名称,这是 Dirk 答案中的 [[1]]。我使用以下方法实现了这个结果:

      my_ellipsis_function <- function(...) {
        input_list <- as.list(substitute(list(...)))
        str(input_list)
        NULL
      }
      my_ellipsis_function(a=1:10,b=11:20,c=21:30)
      
    3. 如上所述,我们可以使用str 来检查函数中有哪些对象。

      my_ellipsis_function <- function(...) {
          input_list <- list(...)
          output_list <- lapply(X=input_list, function(x) {str(x);summary(x)})
          return(output_list)
      }
      my_ellipsis_function(a=1:10,b=11:20,c=21:30)
       int [1:10] 1 2 3 4 5 6 7 8 9 10
       int [1:10] 11 12 13 14 15 16 17 18 19 20
       int [1:10] 21 22 23 24 25 26 27 28 29 30
      $a
         Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
         1.00    3.25    5.50    5.50    7.75   10.00 
      $b
         Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
         11.0    13.2    15.5    15.5    17.8    20.0 
      $c
         Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
         21.0    23.2    25.5    25.5    27.8    30.0 
      

      没关系。让我们看看substitute 版本:

         my_ellipsis_function <- function(...) {
             input_list <- as.list(substitute(list(...)))
             output_list <- lapply(X=input_list, function(x) {str(x);summary(x)})
             return(output_list)
         }
         my_ellipsis_function(a=1:10,b=11:20,c=21:30)
          symbol list
          language 1:10
          language 11:20
          language 21:30
         [[1]]
         Length  Class   Mode 
              1   name   name 
         $a
         Length  Class   Mode 
              3   call   call 
         $b
         Length  Class   Mode 
              3   call   call 
         $c
         Length  Class   Mode 
              3   call   call 
      

      这不是我们需要的。您将需要额外的技巧来处理这些类型的对象(如write.csv)。

    如果你想使用...,那么你应该像list(...)一样在Shane的回答中使用它。

    【讨论】:

      【解决方案2】:

      您可以使用list()将省略号转换为列表,然后对其执行操作:

      > test.func <- function(...) { lapply(list(...), class) }
      > test.func(a="b", b=1)
      $a
      [1] "character"
      
      $b
      [1] "numeric"
      

      所以你的get_list_from_ellipsis 函数只不过是list

      一个有效的用例是在您想要传递未知数量的对象进行操作的情况下(如c()data.frame() 的示例)。但是,当您事先知道每个参数时,使用... 并不是一个好主意,因为它会为参数字符串增加一些歧义和进一步的复杂性(并使任何其他用户不清楚函数签名)。参数列表是函数用户的重要文档。

      否则,当您希望将参数传递给子函数而不将它们全部暴露在您自己的函数参数中时,它也很有用。这可以在函数文档中注明。

      【讨论】:

      • 我知道使用省略号作为子函数参数的传递,但在 R 原语中,按照我描述的方式使用省略号也是常见的做法。事实上,listc 函数都以这种方式工作,但它们都是原语,所以我不能轻易检查它们的源代码以了解它们是如何工作的。
      • rbind.data.frame这样用。
      • 如果list(...)足够了,为什么data.frame这样的R内置函数使用更长的as.list(substitute(list(...)))[-1L]来代替?
      • 因为我没有创建data.frame,所以我不知道答案(也就是说,我确信有一个很好的理由。 )。我在自己的包中为此使用list(),但还没有遇到问题。
      【解决方案3】:

      补充一下 Shane 和 Dirk 的回答:比较很有趣

      get_list_from_ellipsis1 <- function(...)
      {
        list(...)
      }
      get_list_from_ellipsis1(a = 1:10, b = 2:20) # returns a list of integer vectors
      
      $a
       [1]  1  2  3  4  5  6  7  8  9 10
      
      $b
       [1]  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
      

      get_list_from_ellipsis2 <- function(...)
      {
        as.list(substitute(list(...)))[-1L]
      }
      get_list_from_ellipsis2(a = 1:10, b = 2:20) # returns a list of calls
      
      $a
      1:10
      
      $b
      2:20
      

      就目前而言,my_ellipsis_function 中的任一版本似乎都适合您的目的,尽管第一个版本显然更简单。

      【讨论】:

        【解决方案4】:

        你已经给出了一半的答案。考虑

        R> my_ellipsis_function <- function(...) {
        +   input_list <- as.list(substitute(list(...)))
        + }
        R> print(my_ellipsis_function(a=1:10, b=2:20))
        [[1]]
        list
        
        $a
        1:10
        
        $b
        11:20
        
        R> 
        

        所以这从调用中获取了两个参数ab 并将其转换为一个列表。这不是你要求的吗?

        【讨论】:

        • 不是我想要的。这实际上似乎返回了一个列表列表。注意[[1]]。另外,我想知道魔法咒语as.list(substitute(list(...))) 是如何工作的。
        • 内部list(...) 基于参数创建一个list 对象。然后substitute() 为未计算的表达式创建解析树;请参阅此功能的帮助。以及关于 R(或 S)的良好高级文本。这不是小事。
        • 好的,[[-1L]] 部分(来自我的问题)呢?不应该是[[1]]吗?
        • 您需要阅读索引。减号表示“排除”,即print(c(1:3)[-1]) 将仅打印 2 和 3。 L 是一种确保它以整数结尾的新方法,这在 R 源代码中做了很多。
        • 我不需要阅读有关索引的内容,但我确实需要更加注意您显示的命令的输出。 [[1]]$a 索引之间的区别让我认为涉及到嵌套列表。但现在我看到你实际得到的是我想要的列表,但前面多了一个元素。那么[-1L] 是有道理的。无论如何,额外的第一个元素来自哪里?有什么理由我应该使用它而不是简单地使用list(...)
        【解决方案5】:

        这按预期工作。 以下是互动环节:

        > talk <- function(func, msg, ...){
        +     func(msg, ...);
        + }
        > talk(cat, c("this", "is", "a","message."), sep=":")
        this:is:a:message.
        > 
        

        相同,但有一个默认参数:

        > talk <- function(func, msg=c("Hello","World!"), ...){
        +     func(msg, ...);
        + }
        > talk(cat,sep=":")
        Hello:World!
        > talk(cat,sep=",", fill=1)
        Hello,
        World!
        >
        

        如您所见,如果在特定情况下默认值不是您想要的,您可以使用它来将“额外”参数传递给函数中的函数。

        【讨论】:

          猜你喜欢
          • 2021-08-29
          • 2018-01-27
          • 2019-01-28
          • 1970-01-01
          • 2015-11-03
          • 1970-01-01
          • 2013-06-27
          相关资源
          最近更新 更多