在R函数中指定可选参数的“正确”方式

我对在R中使用可选参数编​​写函数的“正确”方法感兴趣。随着时间的推移,我偶然发现了几条代码,它们在这里采用不同的路线,而我找不到合适的(官方)位置关于这个话题。

直到现在,我已经写了这样的可选参数:

fooBar <- function(x,y=NULL){ if(!is.null(y)) x <- x+y return(x) } fooBar(3) # 3 fooBar(3,1.5) # 4.5 

如果只提供了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 

我个人更喜欢第一个版本。 但是,我可以看到两者的好坏。 第一个版本不太容易出错,但第二个版本可以用来包含任意数量的可选项。

有没有一个“正确”的方式来指定R中的可选参数? 到目前为止,我已经解决了第一种方法,但是偶尔也会有点“哈克”的感觉。

您也可以使用missing()来testing是否提供参数y

 fooBar <- function(x,y){ if(missing(y)) { x } else { x + y } } fooBar(3,1.5) # [1] 4.5 fooBar(3) # [1] 3 

说实话,我喜欢OP的第一种方法,实际上用NULL值启动它,然后用is.null (主要是因为它非常简单易懂)来检查它。 这可能取决于人们习惯于编码的方式,但哈德利似乎也支持这种方式:

从Hadley的书“Advanced-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 

有时你想添加一个不重要的默认值,这可能需要几行代码来计算。 而不是在函数定义中插入代码,如果需要,可以使用missing()来有条件地计算它。 然而,这使得很难知道哪些参数是必需的,哪些是可选的,而不仔细阅读文档。 相反,我通常将默认值设置为NULL,并使用is.null()来检查是否提供参数。

这是我的经验法则:

如果可以从其他参数中计算出默认值,则使用如下的默认expression式:

 fun <- function(x,levels=levels(x)){ blah blah blah } 

如果没有使用失踪

 fun <- function(x,levels){ if(missing(levels)){ [calculate levels here] } blah blah blah } 

极less数情况下,用户可能需要指定持续整个R会话的默认值,请使用getOption

 fun <- function(x,y=getOption('fun.y','initialDefault')){# or getOption('pkg.fun.y',defaultValue) blah blah blah } 

如果某些参数取决于第一个参数的类别,则使用S3generics:

 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 } 

只有当您将其他parameter passing给另一个函数时才使用...

 cat0 <- function(...) cat(...,sep = '') 

最后,如果你select使用...而不将点传递到另一个函数, 警告用户你的函数忽略了任何未使用的参数,因为它可能会非常混乱,否则:

 fun <- (x,...){ params <- list(...) optionalParamNames <- letters unusedParams <- setdiff(names(params),optionalParamNames) if(length(unusedParams)) stop('unused parameters',paste(unusedParams,collapse = ', ')) blah blah blah } 

有几种select,它们都不是正式的正确方法,但它们都不是真的不正确,尽pipe它们可以将不同的信息传递给计算机和读取你的代码的其他人。

对于给定的例子,我认为最明确的select是提供一个身份默认值,在这种情况下,做一些事情:

 fooBar <- function(x, y=0) { x + y } 

这是迄今为止所显示的选项中最短的一个,短小可以提高可读性(甚至有时甚至是执行速度)。 很显然,返回的是x和y的总和,你可以看到y没有被赋值,它将是0,当它被添加到x只会导致x。 显然,如果使用比加法更复杂的东西,则需要不同的身份值(如果存在的话)。

我非常喜欢这种方法的一点是,使用args函数时默认的值是什么,甚至看帮助文件(不需要向下滚动到细节,它就在用法)。

这种方法的缺点是缺省值很复杂(需要多行代码),那么可能会降低可读性,以便将所有内容放入默认值,并且missingNULL方法变得更加合理。

当parameter passing给另一个函数,或者使用match.callsys.call函数时,方法之间的一些其他差异将会出现。

所以我猜这个“正确的”方法取决于你打算如何处理这个特定的争论,以及你想传达给你的代码读者的信息。

我会倾向于使用NULL来清晰地表示需要什么以及什么是可选的。 正如Jthorpe所build议的那样,使用默认值取决于其他参数的警告。 当函数被调用时,该值没有被设置,但是当参数被第一次引用时! 例如:

 foo <- function(x,y=length(x)){ x <- x[1:10] print(y) } foo(1:20) #[1] 10 

另一方面,如果在更改x之前引用y:

 foo <- function(x,y=length(x)){ print(y) x <- x[1:10] } foo(1:20) #[1] 20 

这是有点危险的,因为它使得很难跟踪什么“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)) } }