我感兴趣的是在R中使用可选参数编写函数的"正确"方法是什么.随着时间的推移,我偶然发现了几条代码在这里采取了不同的路径,我找不到合适的(官方)位置关于这个话题.
到目前为止,我已经写了这样的可选参数:
fooBar <- function(x,y=NULL){
if(!is.null(y)) x <- x+y
return(x)
}
fooBar(3) # 3
fooBar(3,1.5) # 4.5
Run Code Online (Sandbox Code Playgroud)
如果仅x提供,该函数只返回其参数.它使用NULL第二个参数的默认值,如果该参数恰好不是NULL,则该函数会添加两个数字.
或者,可以像这样编写函数(其中第二个参数需要通过名称指定,但也可以使用unlist(z)或定义z <- sum(...)):
fooBar <- function(x,...){
z <- list(...)
if(!is.null(z$y)) x <- x+z$y
return(x)
}
fooBar(3) # 3
fooBar(3,y=1.5) # 4.5
Run Code Online (Sandbox Code Playgroud)
我个人更喜欢第一个版本.但是,我可以看到两者的好坏.第一个版本不太容易出错,但第二个版本可用于合并任意数量的选项.
是否有一种"正确"的方式来指定R中的可选参数?到目前为止,我已经确定了第一种方法,但两者偶尔会感觉有点"hacky".
Jos*_*ien 117
您还可以missing()用来测试参数y是否提供:
fooBar <- function(x,y){
if(missing(y)) {
x
} else {
x + y
}
}
fooBar(3,1.5)
# [1] 4.5
fooBar(3)
# [1] 3
Run Code Online (Sandbox Code Playgroud)
Lyz*_*deR 50
说实话,我喜欢OP的第一种方法,即实际使用NULL值启动它,然后检查它is.null(主要是因为它非常简单易懂).这可能取决于人们习惯编码的方式,但哈德利似乎也支持这种is.null方式:
从哈德利的书"高级-R"第6章,功能,第84页(用于在线版本检查这里):
您可以使用missing()函数确定是否提供了参数.
i <- function(a, b) {
c(missing(a), missing(b))
}
i()
#> [1] TRUE TRUE
i(a = 1)
#> [1] FALSE TRUE
i(b = 2)
#> [1] TRUE FALSE
i(1, 2)
#> [1] FALSE FALSE
Run Code Online (Sandbox Code Playgroud)
有时您想要添加一个非平凡的默认值,这可能需要几行代码来计算.您可以使用missing()在需要时有条件地计算它,而不是在函数定义中插入该代码.但是,这使得很难知道哪些参数是必需的,哪些是可选的而不仔细阅读文档.相反,我通常将默认值设置为NULL并使用is.null()来检查参数是否已提供.
Jth*_*rpe 22
这些是我的经验法则:
如果可以从其他参数计算默认值,请使用默认表达式,如下所示:
fun <- function(x,levels=levels(x)){
blah blah blah
}
Run Code Online (Sandbox Code Playgroud)
如果否则使用缺失
fun <- function(x,levels){
if(missing(levels)){
[calculate levels here]
}
blah blah blah
}
Run Code Online (Sandbox Code Playgroud)
在极少数情况下,用户可能想要指定一个持续整个R会话的默认值,请使用getOption
fun <- function(x,y=getOption('fun.y','initialDefault')){# or getOption('pkg.fun.y',defaultValue)
blah blah blah
}
Run Code Online (Sandbox Code Playgroud)
如果某些参数取决于第一个参数的类,则使用S3泛型:
fun <- function(...)
UseMethod(...)
fun.character <- function(x,y,z){# y and z only apply when x is character
blah blah blah
}
fun.numeric <- function(x,a,b){# a and b only apply when x is numeric
blah blah blah
}
fun.default <- function(x,m,n){# otherwise arguments m and n apply
blah blah blah
}
Run Code Online (Sandbox Code Playgroud)
使用...仅当您在传递附加参数给另一个函数
cat0 <- function(...)
cat(...,sep = '')
Run Code Online (Sandbox Code Playgroud)
最后,如果您选择使用...而不将点传递到另一个函数,请警告用户您的函数忽略了任何未使用的参数,因为它可能会非常混乱:
fun <- (x,...){
params <- list(...)
optionalParamNames <- letters
unusedParams <- setdiff(names(params),optionalParamNames)
if(length(unusedParams))
stop('unused parameters',paste(unusedParams,collapse = ', '))
blah blah blah
}
Run Code Online (Sandbox Code Playgroud)
虽然它们可以向计算机和其他读取代码的人传达不同的信息,但有几种选择并且它们都不是官方正确的方式,并且它们都不是真的不正确.
对于给定的示例,我认为最明确的选择是提供身份默认值,在这种情况下执行以下操作:
fooBar <- function(x, y=0) {
x + y
}
Run Code Online (Sandbox Code Playgroud)
这是迄今为止所示选项中最短的选项,而且简洁性有助于提高可读性(有时甚至可以提高执行速度).很明显,返回的是x和y之和,你可以看到y没有给出一个0的值,当加到x时它只会产生x.显然,如果使用比添加更复杂的东西,则需要不同的身份值(如果存在).
我真正喜欢这种方法的一件事是它很清楚使用该args函数时的默认值,甚至查看帮助文件(您不需要向下滚动到详细信息,它就在使用中).
这种方法的缺点是当默认值很复杂(需要多行代码)时,它可能会降低可读性,试图将所有这些都放入默认值,并且missing或者NULL方法变得更加合理.
当参数传递给另一个函数或使用match.call或sys.call函数时,这些方法之间的一些其他差异将出现.
因此,我认为"正确"的方法取决于您打算如何处理该特定参数以及您希望向您的代码读者传达哪些信息.
小智 6
我倾向于使用NULL来明确所需内容和可选内容.关于使用依赖于其他参数的默认值的一个警告,正如Jthorpe所建议的那样.调用函数时,不会设置该值,但首次引用该参数时!例如:
foo <- function(x,y=length(x)){
x <- x[1:10]
print(y)
}
foo(1:20)
#[1] 10
Run Code Online (Sandbox Code Playgroud)
另一方面,如果在更改x之前引用y:
foo <- function(x,y=length(x)){
print(y)
x <- x[1:10]
}
foo(1:20)
#[1] 20
Run Code Online (Sandbox Code Playgroud)
这有点危险,因为它很难跟踪正在初始化的"y",好像它没有在函数的早期调用.
只是想指出内置sink函数有很好的例子来设置函数中的参数:
> sink
function (file = NULL, append = FALSE, type = c("output", "message"),
split = FALSE)
{
type <- match.arg(type)
if (type == "message") {
if (is.null(file))
file <- stderr()
else if (!inherits(file, "connection") || !isOpen(file))
stop("'file' must be NULL or an already open connection")
if (split)
stop("cannot split the message connection")
.Internal(sink(file, FALSE, TRUE, FALSE))
}
else {
closeOnExit <- FALSE
if (is.null(file))
file <- -1L
else if (is.character(file)) {
file <- file(file, ifelse(append, "a", "w"))
closeOnExit <- TRUE
}
else if (!inherits(file, "connection"))
stop("'file' must be NULL, a connection or a character string")
.Internal(sink(file, closeOnExit, FALSE, split))
}
}
Run Code Online (Sandbox Code Playgroud)