【发布时间】:2023-07-11 03:33:01
【问题描述】:
我想创建一个函数myfun,它只能在另一个函数中使用,在我的例子中是dplyrs mutate 或summarise。我更不想依赖dplyrs 内部结构(例如mask$...)。
我想出了一个快速而肮脏的解决方法:一个函数search_calling_fn 检查调用堆栈中的所有函数名称并在调用函数中查找特定模式。
search_calling_fn <- function(pattern) {
call_st <- lapply(sys.calls(), `[[`, 1)
res <- any(unlist(lapply(call_st, function(x) grepl(pattern, x, perl = TRUE))))
if (!res) {
stop("`myfun()` must only be used inside dplyr::mutate or dplyr::summarise")
} else {
return()
}
}
正如下面两个示例所示,这可以正常工作 (dplyr = 1.0.0)
library(dplyr)
myfun <- function() {
search_calling_fn("^mutate|^summarise")
NULL
}
# throws as expected no error
mtcars %>%
mutate(myfun())
myfun2 <- function() {
search_calling_fn("^select")
NULL
}
# throws as expected an error
mtcars %>%
mutate(myfun2())
这种方法有一个漏洞:myfun 可以从具有相似名称但不是dplyr 函数的函数中调用。我想知道如何检查调用堆栈上的函数来自哪个命名空间。 rlang 有一个函数 call_ns 但这只有在使用 package::... 显式调用该函数时才有效。此外,当使用 mutate 时,调用堆栈上有 mutate_cols 一个内部函数和一个 S3 方法 mutate.data.frame - 两者似乎都使获取命名空间变得更加复杂。
再想一想,我想知道是否有更好、更正式的方法来实现相同的结果:只允许在 dplyrs mutate 或 summarise 内调用 myfun。
无论函数如何调用,该方法都应该有效:
mutatedplyr::mutate
补充说明
在讨论了@r2evans 的回答后,我意识到一个解决方案应该通过以下测试:
library(dplyr)
myfun <- function() {
search_calling_fn("^mutate|^summarise")
NULL
}
# an example for a function masking dplyr's mutate
mutate <- function(df, x) {
NULL
}
# should throw an error but doesn't
mtcars %>%
mutate(myfun())
所以检查函数不应该只看调用栈,还要尝试查看调用栈上的函数来自哪个包。有趣的是,RStudios 调试器会显示调用堆栈上每个函数的命名空间,即使是内部函数也是如此。我想知道它是怎么做到的,因为environment(fun)) 只对导出的函数起作用。
【问题讨论】:
-
Nitpick:你在函数
search_calling_fn的末尾缺少一个关闭}。 -
感谢您发现这一点!我更正了。
-
您的示例
mutate代码将永远失败,因为x懒惰;因为它从未被使用过,它永远不会“实现”,所以myfun永远不会被调用。 ...但我明白你的意思,getAnywhere在我的回答中有点过于急切了。
标签: r function dplyr callstack rlang