首页 > 解决方案 > 如何确保参数是按名称而不是按位置调用的?

问题描述

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

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

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

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

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 帖子(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))) #https://stackoverflow.com/a/17257053/3888000
    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 包于 2021-10-20 创建(v2.0.1)

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

PS:最后一行看起来像reprex().

标签: rargumentsassertionoptional-parameters

解决方案


您可以使用该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")

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


推荐阅读