我有一个R函数:
myFunc <- function(x, base='') {
}
我现在正在扩展该函数,允许一组任意的额外参数:
myFunc <- function(x, base='', ...) {
}
如何在base
参数上禁用部分参数匹配?我无法将...
放在base=''
之前,因为我想保持向后兼容性该函数的函数(通常称为myFunction('somevalue', 'someothervalue')
,而base
未明确命名。)
我通过调用我的函数来惹恼:
myFunc(x, b='foo')
我希望这意味着base='', b='foo'
,但R使用部分匹配并假定为base='foo'
。
我是否可以在myFunc
中插入一些代码来确定传入的参数名称,并且只将完全“base”与base
参数匹配,否则分组它作为...
的一部分?
答案 0 :(得分:6)
这是一个想法:
myFunc <- function(x, .BASE = '', ..., base = .BASE) {
base
}
## Takes fully matching named arguments
myFunc(x = "somevalue", base = "someothervalue")
# [1] "someothervalue"
## Positional matching works
myFunc("somevalue", "someothervalue")
# [1] "someothervalue"
## Partial matching _doesn't_ work, as desired
myFunc("somevalue", b="someothervalue")
# [1] ""
答案 1 :(得分:1)
由@Hemmo提示解决此问题的另一种方法。
使用sys.call()
了解myFunc
的调用方式(没有部分参数匹配,请使用match.call
):
myFunc <- function(x, base='', ...) {
x <- sys.call() # x[[1]] is myFunc, x[[2]] is x, ...
argnames <- names(x)
if (is.null(x$base)) {
# if x[[3]] has no argname then it is the 'base' argument (positional)
base <- ifelse(argnames[3] == '', x[[3]], '')
}
# (the rest of the `...` is also in x, perhaps I can filter it out by
# comparing argnames against names(formals(myFunc)) .
}
答案 2 :(得分:1)
最简单的方法是简单地设置一些 R options。尤其是
warnPartialMatchArgs
:
合乎逻辑。如果为 true,则在参数匹配中使用部分匹配时发出警告。warnPartialMatchAttr
:
合乎逻辑。如果为 true,则在通过 attr 提取属性时使用部分匹配时发出警告。warnPartialMatchDollar
:
合乎逻辑。如果为 true,则在 $ 使用部分匹配进行提取时发出警告。设置这些变量现在会引发警告,如果您愿意,您可以将其转化为错误。
答案 3 :(得分:0)
这有点晚了。但为了将来的参考,我有了这个想法。
使用引用名称可以避免部分匹配。在函数中,使用sys.call()参数。
{{1}}
答案 4 :(得分:0)
可以使用sys.call()
来访问调用者给出的函数参数。必须小心,因为sys.call()
不评估参数,而是为您提供调用的表达式。当使用...
作为参数调用函数时,这会变得特别困难:sys.call()
只包含...
,而不是它们的值。但是,可以将sys.call()
作为另一个函数的参数列表进行评估,例如list()
。这会评估所有的承诺,并抛弃一些信息,但在试图规避R的内部匹配时,我无法看到如何解决这个问题。
一个想法是模拟严格匹配。如果作为函数中的第一个命令调用,我附加了一个辅助函数,它正是这样做的:
fun = function(x, base='', ...) {
strictify() # make matching strict
list(x, base, ...)
}
这会过滤掉不匹配的参数:
> fun(10, b = 20)
[[1]]
[1] 10
[[2]]
[1] ""
$b
[1] 20
并且也适用于大多数其他情况(带或不带...
,带有...
右边的参数,带参数默认值)。它唯一不起作用的是非标准评估,例如,在尝试使用substitute(arg)
获取参数的表达式时。
strictify <- function() {
# remove argument values from the function
# since matching already happened
parenv <- parent.frame() # environment of the calling function
rm(list=ls(parenv), envir=parenv) # clear that environment
# get the arguments
scall <- sys.call(-1) # 'call' of the calling function
callingfun <- scall[[1]]
scall[[1]] <- quote(`list`)
args <- eval.parent(scall, 2) # 'args' is now a list with all arguments
# if none of the argument are named, we need to set the
# names() of args explicitly
if (is.null(names(args))) {
names(args) <- rep("", length(args))
}
# get the function header ('formals') of the calling function
callfun.object <- eval.parent(callingfun, 2)
callfun.header <- formals(callfun.object)
# create a dummy function that just gives us a link to its environment.
# We will use this environment to access the parameter values. We
# are not using the parameter values directly, since the default
# parameter evaluation of R is pretty complicated.
# (Consider fun <- function(x=y, y=x) { x } -- fun(x=3) and
# fun(y=3) both return 3)
dummyfun <- call("function", callfun.header, quote(environment()))
dummyfun <- eval(dummyfun, envir=environment(callfun.object))
parnames <- names(callfun.header)
# Sort out the parameters that didn't match anything
argsplit <- split(args, names(args) %in% c("", parnames))
matching.args <- c(list(), argsplit$`TRUE`)
nonmatching.arg.names <- names(argsplit$`FALSE`)
# collect all arguments that match something (or are just
# positional) into 'parenv'. If this includes '...', it will
# be overwritten later.
source.env <- do.call(dummyfun, matching.args)
for (varname in ls(source.env, all.names=TRUE)) {
parenv[[varname]] <- source.env[[varname]]
}
if (!"..." %in% parnames) {
# Check if some parameters did not match. It is possible to get
# here if an argument only partially matches.
if (length(nonmatching.arg.names)) {
stop(sprintf("Nonmatching arguments: %s",
paste(nonmatching.arg.names, collapse=", ")))
}
} else {
# we manually collect all arguments that fall into '...'. This is
# not trivial. First we look how many arguments before the '...'
# were not matched by a named argument:
open.args <- setdiff(parnames, names(args))
taken.unnamed.args <- min(which(open.args == "...")) - 1
# We throw all parameters that are unmatched into the '...', but we
# remove the first `taken.unnamed.args` from this, since they go on
# filling the unmatched parameters before the '...'.
unmatched <- args[!names(args) %in% parnames]
unmatched[which(names(unmatched) == "")[seq_len(taken.unnamed.args)]] <- NULL
# we can just copy the '...' from a dummy environment that we create
# here.
dotsenv <- do.call(function(...) environment(), unmatched)
parenv[["..."]] <- dotsenv[["..."]]
}
}
还可以使用将正常匹配函数转换为严格匹配函数的函数,例如
strict.fun = strictificate(fun)
但这会使用相同的技巧。
答案 5 :(得分:-2)
这是一个令人作呕的可怕黑客,但它可能会完成工作:
myFunc <- function(x, base='', b=NULL, ba=NULL, bas=NULL, ...) {
dots <- list(b=b, ba=ba, bas=bas, ...)
#..
}