在编写自己的函数时,如何使用R's省略号特性?

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(...)。然而,这两者做的事情并不完全相同。(关于差异,请参见答案中的例子。)谁能告诉我它们之间的实际区别是什么,我应该用哪一个?

65167 次浏览

你已经回答了一半了。考虑

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,并将其转换为一个列表。这不是你要的吗?

你可以用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()的例子中)。然而,当你事先知道每个形参时,使用...并不是一个好主意,因为它会给参数字符串增加一些模糊性和进一步的复杂性(并使函数签名对任何其他用户来说都不清楚)。参数列表对于函数用户来说是一个重要的文档。

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

再补充一下谢恩和德克的回答:比较是很有趣的

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中的目的,尽管第一个版本显然更简单。

我看了回答和评论,发现有几件事没有被提到:

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

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

    object用于对列名做一些魔术,但x用于创建最终的data.frame 要使用未赋值的...实参,请参阅write.csv代码,其中使用了match.call

  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. As above we can use str to check what objects are in a function.

    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)

如果你想使用...,那么你应该像在Shane answer中那样使用它,通过list(...)

按预期工作。 下面是一个交互会话:

> 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!
>

正如你所看到的,如果在特定情况下默认值不是你想要的,你可以使用这个来在你的函数中传递'额外'参数。