【问题标题】:"Correct" way to specifiy optional arguments in R functions在 R 函数中指定可选参数的“正确”方式
【发布时间】:2015-04-06 20:37:58
【问题描述】:

我对在 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 &lt;- 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 中是否有一种“正确”的方式来指定可选参数?到目前为止,我已经确定了第一种方法,但两者有时都会觉得有点“hacky”。​​

【问题讨论】:

  • 查看xy.coords的源代码,了解常用方法。
  • Carl Witthoftl提到的xy.coords的源代码可以在xy.coords找到

标签: r function


【解决方案1】:

这个怎么样?

fun <- function(x, ...){
  y=NULL
  parms=list(...)
  for (name in names(parms) ) {
    assign(name, parms[[name]])
  }
  print(is.null(y))
}

那就试试吧:

> fun(1,y=4)
[1] FALSE
> fun(1)
[1] TRUE

【讨论】:

    【解决方案2】:

    只是想指出,内置的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))
        }
    }
    

    【讨论】:

    • 我不知道如果我调用上述函数而不指定“type”的值,“type”的默认值是什么?
    【解决方案3】:

    我倾向于使用 NULL 来明确什么是必需的,什么是可选的。正如 Jthorpe 所建议的,关于使用依赖于其他参数的默认值的警告。该值不是在调用函数时设置的,而是在第一次引用参数时设置的!例如:

    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”,就好像它没有在函数的早期调用一样。

    【讨论】:

      【解决方案4】:

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

      来自 Hadley 的书“Advanced-R”第 6 章,函数,第 84 页(在线版本检查here):

      您可以使用 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() 检查是否提供了参数。

      【讨论】:

      • 有趣。这听起来很合理,但是您是否曾经对函数的哪些参数是必需的,哪些是可选的感到困惑?我不确定我是否曾经真的有过那种经历......
      • @JoshO'Brien 老实说,我认为这两种编码风格都没有问题,至少它从来都不是主要问题,可能是因为文档或阅读源代码。这就是为什么我主要说这实际上是您习惯的编码风格的问题。我使用NULL 方式已经有一段时间了,可能这就是为什么我在看到源代码时更习惯它的原因。这对我来说似乎更自然。也就是说,正如您所说,base R 采用这两种方法,所以这实际上归结为个人偏好。
      • 到现在为止,我真的希望我可以将两个答案标记为正确,因为我真正使用is.nullmissing 得出的结果取决于上下文和参数的用途。跨度>
      • 没关系@SimonG,谢谢:)。我同意这两个答案都非常好,而且有时确实取决于上下文。这是一个非常好的问题,我相信答案提供了非常好的信息和知识,无论如何这是这里的主要目标。
      【解决方案5】:

      这些是我的经验法则:

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

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

      如果否则使用missing

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

      罕见的情况下,用户可能希望指定默认值 持续整个 R 会话,请使用 getOption

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

      如果根据第一个参数的类应用某些参数, 使用 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 
      }
      

      仅当您将其他参数传递给 另一个函数

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

      最后,如果您选择使用 ... 而不将点传递给另一个函数,警告用户您的函数忽略了任何未使用的参数,因为它可能非常 em> 否则会令人困惑:

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

      【讨论】:

      • s3 方法选项也是我首先想到的事情之一
      • 回想起来,我已经很喜欢OP在函数签名中分配NULL的方法,因为它更方便地制作chain的函数。
      【解决方案6】:

      有几个选项,它们都不是官方正确的方法,也没有一个是真正不正确的,尽管它们可以向计算机和其他阅读您的代码的人传达不同的信息。

      对于给定的示例,我认为最明确的选择是提供标识默认值,在这种情况下,请执行以下操作:

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

      这是迄今为止显示的选项中最短的一个,简短有助于提高可读性(有时甚至提高执行速度)。很明显,返回的是 x 和 y 的总和,您可以看到 y 没有被赋予一个值为 0 的值,当添加到 x 时只会产生 x。显然,如果使用比加法更复杂的东西,则需要不同的标识值(如果存在的话)。

      我真正喜欢这种方法的一点是,在使用args 函数时,甚至查看帮助文件(您无需向下滚动到详细信息,它就在用法中)。

      这种方法的缺点是当默认值很复杂(需要多行代码)时,尝试将所有这些都放入默认值可能会降低可读性,missingNULL 方法会变成更合理。

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

      所以我猜“正确”的方法取决于您打算如何处理该特定参数以及您想向代码读者传达什么信息。

      【讨论】:

        【解决方案7】:

        您还可以使用missing() 来测试是否提供了参数y

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

        【讨论】:

        • 我更喜欢错过。特别是如果你有很多 NULL 默认值,你的包文档中不会有 x = NULL、y = NULL、z = NULL
        • @rawr missing() 在“说出它的意思”的意义上也更具表现力。此外,它还允许用户在有意义的地方传入 NULL 值!
        • 对我来说,以这种方式使用缺失有很大的缺点:在浏览函数参数时,您无法再看到哪些参数是必需的,哪些是选项。
        • @param x numeric; something something; @param y numeric; **optional** something something; @param z logical; **optional** something something
        • missing() 在您想将参数从一个函数传递到另一个函数时非常糟糕。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-20
        相关资源
        最近更新 更多