R: 检查是否提供了必需/必填参数

3
我希望能够在调用R函数时检查所有未指定默认值的参数是否已经指定。这对我来说似乎是一个明智的做法,因为它避免了在发现缺少一个值后(这可能会在很多处理之后)函数失败的情况。
一种实现此任务的方法是为每个参数编写一个if (missing(arg))语句,但这需要保持函数参数和上述语句的一致性,因此我正在寻找更好的解决方案。
目前我使用以下函数,在大多数情况下可以工作,但不是全部情况。
# check for required arguments by getting arguments for the
# definition of the calling function and comparing to the arguments 
# in the function call
check_required_args <- function () {
    def    <- sys.function(-1)
    f_args <- formals(def)
    f_args <- f_args[sapply(f_args, is.name)]  # remove arguments with defaults
    f_args <- names(f_args)
    f_args <- f_args[f_args != '...'] # remove ellipsis argument if present

    call   <- match.call(definition=def, call=sys.call(-1))
    f_name <- call[1]
    c_args <- names(as.list(call[-1]))

    for(n in f_args) {
        if (!n %in% c_args) {
            stop("Argument '", n, "' missing from call to function ", 
                f_name, "()", call.=FALSE)
        }
    }
}

f <- function(a, b, c=2) check_required_args()
f(a=1) # should fail (missing argument b)
f(2, 3) # should work
f(2, c=5) # should fail (missing argument b)
f(2, 3, 4) # should work


f <- function(a, b, ...) f2(a, b, ...)
f2 <- function(a, b, c, ...) check_required_args()

f2(a=1, b=2, c=3) # should work
f2(a=1, b=2) # should fail (missing argument c for function f2)
f(a=1, b=2, c=3) # should work
f(a=1, b=2) # should fail  (missing argument c for function f2)

这个函数能否改进,以使其在所有情况下都能工作?如果不能,是否有更适合执行此检查的解决方案?


你为什么觉得有必要这样做呢?R的惰性求值机制非常棒,因为不需要的参数可以被忽略。更何况,你首先将参数放入函数的形式参数中的原因就是要使用它们!所以,对于你的问题,正确的答案是:不,这不是一个“明智”的做法。 - Carl Witthoft
2
我有几个函数,在函数的后面才使用某个参数,但此时由于缺少该参数,函数将抛出错误。当函数需要运行数小时时,这真的很烦人,因此在某些情况下,我想在进一步评估之前检查是否指定了所有必需的参数。对我来说,这似乎是合理的? - waferthin
不,看起来你正在犯基本的编程错误。首先,不要将太多处理放入单个函数中,以至于运行时间过长:编写多个函数并将它们脚本化。在我看来,“好”的函数不应该需要大量的输入参数;您可能需要考虑将(经过验证的)数据集建立到一个列表变量中。其次,在某个时候,你必须进行输入验证,不仅要验证其存在性,还要验证其正确的数据类型(例如,字符 vs. 数值)。 - Carl Witthoft
1个回答

4
也许是这个吗?
check_required_args <- function (fun = sys.function(-1), ncall = 3) {
  f_args <- formals(fun)
  f_args <- f_args[vapply(f_args, is.symbol, FUN.VALUE = TRUE)]
  f_args <- names(f_args)
  f_args <- setdiff(f_args, "...")
  test <- vapply(f_args, 
                 function(x) missingArg(as.name(x), envir = parent.frame(ncall), eval = TRUE), 
                 FUN.VALUE = TRUE)
  stopifnot(!any(test))
  return(invisible(NULL))     
}


f <- function(a, b, c=2) {
  check_required_args()
  return("Hello!")
}
f(a=1) # should fail (missing argument b)
#Error: !any(test) is not TRUE

f(2, 3) # should work
#[1] "Hello!"

f(2, c=5) # should fail (missing argument b)
# Error: !any(test) is not TRUE

f(2, 3, 4) # should work
#[1] "Hello!"

x <- 1
f(a=x, 3)
#[1] "Hello!"


f <- function(a, b, ...) f2(a, b, ...)
f2 <- function(a, b, c, ...) {
  check_required_args()
  return("Hello!")
}

f2(a=1, b=2, c=3) # should work
#[1] "Hello!"

f2(a=1, b=2) # should fail (missing argument c for function f2)
#Error: !any(test) is not TRUE 

f(a=1, b=2, c=3) # should work
#[1] "Hello!"

f(a=1, b=2) # should fail  (missing argument c for function f2)
#Error: !any(test) is not TRUE 

编辑:

你可能想使用get来检查是否存在:

check_required_args <- function (fun = sys.function(-1), ncall = 3) {
  f_args <- formals(fun)
  f_args <- f_args[vapply(f_args, is.symbol, FUN.VALUE=TRUE)]
  f_args <- names(f_args)
  f_args <- setdiff(f_args, "...")
  test <- lapply(f_args, 
                 function(x) {
                   get(x, envir = parent.frame(ncall), inherits = TRUE)
                   return(NULL)
                   })
  #possibly use a for loop instead
  #wrap in tryCatch for customized error messages

}

f <- function(a, b, ...) f2(a, b, ...)
f2 <- function(a, b, c, ...) {
  check_required_args()
  return("Hello!")
}

f(c=2)
#Error in get(x, envir = parent.frame(ncall), inherits = TRUE) : 
#  argument "a" is missing, with no default 

如果您不想检查封闭的框架,请将 inherits = FALSE 设置。

谢谢。我认为 f(c=2) 也应该失败,但它在这里通过了测试? - waferthin
我不确定如何按照您的期望来修复它。abf2中并没有丢失,但是在f中确实丢失了。因此,在f2中进行检查无法告诉您它们在f中是否丢失。我是否正确理解您希望跟踪调用堆栈并检查所有封闭函数?在f2开头强制评估参数可能更容易些。 - Roland
请注意,这可能会导致性能问题(特别是如果参数可以是大对象)。 - Roland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接