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)

Edit

似乎有两种方法可以做到这一点。他们as.list(替代(列表(…)))[1 l]和列表(…)。然而,这两者做的事情并不完全相同。(关于差异,请参见答案中的例子。)谁能告诉我它们之间的实际区别是什么,我应该用哪一个?


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

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> 

这从调用中取了两个参数a和b并将其转换为一个列表。这不是你要的吗?


你可以用list()将省略号转换成一个列表,然后对它执行你的操作:

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

$b
[1] "numeric"

所以你的get_list_from_ellipsis函数不过是一个列表。

一个有效的用例是在您想要传入未知数量的对象用于操作的情况下(如在您的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

with

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中,这两个版本似乎都适合您的目的,不过第一个版本显然更简单。


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

data.frame uses list(...) version. Fragment of the code: object <- as.list(substitute(list(...)))[-1L] mrn <- is.null(row.names) x <- list(...) object is used to do some magic with column names, but x is used to create final data.frame. For use of unevaluated ... argument look at write.csv code where match.call is used. As you write in comment result in Dirk answer is not a list of lists. Is a list of length 4, which elements are language type. First object is a symbol - list, second is expression 1:10 and so on. That explain why [-1L] is needed: it removes expected symbol from provided arguments in ... (cause it is always a list). As Dirk states substitute returns "parse tree the unevaluated expression". When you call my_ellipsis_function(a=1:10,b=11:20,c=21:30) then ... "creates" a list of arguments: list(a=1:10,b=11:20,c=21:30) and substitute make it a list of four elements: List of 4 $ : symbol list $ a: language 1:10 $ b: language 11:20 $ c: language 21:30 First element doesn't have a name and this is [[1]] in Dirk answer. I achieve this results using: 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) 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 It's ok. Lets see substitute version: 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 Isn't what we needed. You will need additional tricks to deal with these kind of objects (as in write.csv).

如果你想用…那么你应该像在Shane answer中那样使用它,by 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!
>

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