如何确保参数是按名称而不是按位置调用的?

How to ensure that parameters have been called by name and not by position?

我正在维护一个 package 以单个函数为中心的函数,其中包含一些强制参数以及许多可选参数。

随着我的函数的成熟,可选参数的顺序正在发生变化,因此按顺序调用它们将导致重大更改。

如果后面这些参数是按位置而不是按名称调用的,我想抛出一个warning/error(不确定什么是最好的)。

这是一些具有预期输出的伪代码:

crosstable = function(data, cols=NULL, ..., by=NULL, opt1=FALSE, opt2=FALSE, opt3=FALSE){
    warn_if_unnamed(by)
    stop_if_unnamed(opt1)
    stop_if_unnamed(opt2)
    stop_if_unnamed(opt3)
    doStuff(data, cols, by, opt1, opt2, opt3)
}
crosstable(mtcars, c(cyl, am), by=vs, opt3=TRUE) #OK
crosstable(mtcars, c(cyl, am), by=vs, TRUE)      #error as `opt1` might become `opt56` in the future
crosstable(mtcars, c(cyl, am), vs, opt2=TRUE)    #warning (?) as `by` will not move but it would be clearer

我怎样才能做到这一点?

编辑:

感谢@user2554330 和其他一些 SO post (here),我终于让它工作了,尽管如果与管道一起使用它不会工作:

warn_if_unnamed <- function(argname){
    .call = sys.call(-1)
    f = get(as.character(.call[[1]]), mode="function", sys.frame(-2))
    mc = names(as.list(match.call(definition=f, call=.call))) #
    sc = names(as.list(.call))
    if(argname %in% mc && !argname %in% sc){
        warning(argname," is referenced by position, not name")
    }
}
myfun = function(x, y=NULL, opt1=FALSE, opt2=FALSE, opt3=FALSE){
    warn_if_unnamed("opt1")
    warn_if_unnamed("opt2")
    warn_if_unnamed("opt3")
    invisible()
}
myfun(1, 2)
myfun(1, 2, T, opt2=1)
#> Warning in warn_if_unnamed("opt1"): opt1 is referenced by position, not name
myfun(1, 2, opt1=T, 1, opt3)
#> Warning in warn_if_unnamed("opt2"): opt2 is referenced by position, not name
#> Warning in warn_if_unnamed("opt3"): opt3 is referenced by position, not name
myfun(1, 2, opt2=T, 1, opt3)
#> Warning in warn_if_unnamed("opt1"): opt1 is referenced by position, not name
#> Warning in warn_if_unnamed("opt1"): opt3 is referenced by position, not name

reprex package (v2.0.1)

于 2021-10-20 创建

不过我可能会进行一些重构以将警告集中到一个警告中。

PS:最后一行看起来像是 reprex() 中的错误。

您可以使用 sys.call() 函数查看您的函数是如何被调用的,并 match.call() 查看 R 如何将参数与参数匹配。所以代码为 warn_if_unnamed(by) 将是:

if ("by" %in% names(as.list(match.call())) &&
   !"by" %in% names(as.list(sys.call())))
  warning("'by' should be named")

可以将它放在一个函数中;您需要使用 sys.call()match.call()where 参数来查看函数调用者的参数,而不是 warn_if_unnamed 本身的参数。

func <- function(data, cols = NULL, ...) {
  opt_args <- list(...)
  
  if(length(opt_args) > 0 && is.null(names(opt_args))) {
    stop("Optional arguments must be named")
  }
  
  allowed_args <- c("opt1", "opt2")
  if(length(setdiff(names(opt_args), allowed_args)) > 0) {
    warning("Additional unknown arguments are ignored")
  }
  
  opt_args
}

# ok
func(iris, c("Sepal.Length", "Sepal.Width"))
#> list()
func(iris, c("Sepal.Length", "Sepal.Width"), opt1 = "foo")
#> $opt1
#> [1] "foo"

# warning 
func(iris, c("Sepal.Length", "Sepal.Width"), opt3 = "foo")
#> Warning in func(iris, c("Sepal.Length", "Sepal.Width"), opt3 = "foo"):
#> Additional unknown arguments are ignored
#> $opt3
#> [1] "foo"

# error
func(iris, c("Sepal.Length", "Sepal.Width"), "foo")
#> Error in func(iris, c("Sepal.Length", "Sepal.Width"), "foo"): Optional arguments must be named

reprex package (v2.0.1)

于 2021-10-20 创建

这可能是一个 warn_if_unnamed 函数:

warn_if_unnamed <- function(argname){
  arguments <- as.list(sys.call(which = -1)) # get arguments of sys.call
  arg_name <- deparse(substitute(argname))# get variable name
  arg_named <- arg_name %in% names(arguments)
  if(!arg_named){
    warning(arg_name," is referenced by position, not name")
  }
}

使用示例:

myfun <- function( arg1,arg2=2,arg3=3 ) {
  warn_if_unnamed(arg2)
}
myfun(1,2)

#In warn_if_unnamed(arg2) : arg2 is referenced by position, not name

错误如果未命名可以类似于 error() 而不是 warning()