在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
函数时默认的值是什么,甚至看帮助文件(不需要向下滚动到细节,它就在用法)。
这种方法的缺点是缺省值很复杂(需要多行代码),那么可能会降低可读性,以便将所有内容放入默认值,并且missing
或NULL
方法变得更加合理。
当parameter passing给另一个函数,或者使用match.call
或sys.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)) } }