我有一个函数,该函数应该根据调用它的环境使用一个值:
Foo <- function(a) {
return(a + b)
}
在全局环境中我现在可以简单地做
b <- 1
Foo(2)
# 3
真正的函数将采用参数列表,因此我使用 do call,很简单:
do.call(Foo, list(a = 2)
# 3
在 local
block (或 test_that
block )中运行它,这不会按照我想要的方式工作(无论哪种方式)。
local({
b <- 3
print(Foo(2))
print(do.call(Foo, list(a = 2)))
})
# [1] 3
# [1] 3
这将返回 3
,我想要 5
。如果我没有定义或删除全局 b
,该函数将会失败,因为它找不到该符号。
我尝试为 do.call
方法提供不同的环境(environment()
、parent.env()
,...)使用 quote = TRUE
和不使用但无济于事。
有没有办法强制 Foo
通过将其作为环境传递来查找本地 block 中的变量?
最佳答案
1) 由于 b 没有在 Foo 中定义,因此 Foo 将在定义 Foo 的环境中查找 b,而不是在调用它的环境中。
您可以重新定义 Foo 的环境。这将创建 Foo 的副本,以便在本地环境中查找其中的自由变量。没有使用任何包。
local({
b <- 3
environment(Foo) <- environment()
print(Foo(2))
print(do.call(Foo, list(a = 2)))
})
## [1] 5
## [1] 5
2) 如果可以修改 Foo,那么其他可能性是通过传递 b 作为附加参数或传递环境作为附加参数来修改它,并让 Foo 在该环境中评估 b。
Foo2 <- function(a, b) {
return(a + b)
}
local({
b <- 3
print(Foo2(2, b))
print(do.call(Foo2, list(a = 2, b = b)))
})
## [1] 5
## [1] 5
3) 或
Foo3 <- function(a, envir = parent.frame()) {
return(a + envir$b)
}
local({
b <- 3
print(Foo3(2))
print(do.call(Foo3, list(a = 2)))
})
## [1] 5
## [1] 5
4) 上述内容的一个变体,仅涉及修改 Foo 的签名而不是其主体,如下所示(或使用 get("b",parent.frame())
(如果您想允许它也查看父框架的祖先)。
Foo4 <- function(a, b = parent.frame()$b) {
return(a + b)
}
local({
b <- 3
print(Foo4(2))
print(do.call(Foo4, list(a = 2)))
})
## [1] 5
## [1] 5
5) 另一种方法是使用 trace
将语句注入(inject) Foo,然后将其删除。
local({
b <- 3
on.exit(untrace(Foo))
trace(Foo, bquote(b <- .(b)), print = FALSE)
print(Foo(2))
print(do.call(Foo, list(a = 2)))
})
## [1] 5
## [1] 5
6) 如果我们将 Foo 的主体包装在 eval.parent(substitute({...})) 中,这将有效地将其注入(inject)到调用者中,使其能够访问 b。另请参阅从 R News 1/3 第 11 页开始的 Thomas Lumley 文章.
Foo6 <- function(a) eval.parent(substitute({
return(a + b)
}))
local({
b <- 3
print(Foo6(2))
print(do.call(Foo6, list(a = 2)))
})
## [1] 5
## [1] 5
7) 这实际上与引擎盖下的 (6) 相同,只是它包裹得很好。这是这里唯一使用包的一个。
library(gtools)
Foo7 <- defmacro(a, expr = {
return(a + b)
})
local({
b <- 3
print(Foo7(2))
print(do.call(Foo7, list(a = 2)))
})
## [1] 5
## [1] 5
关于r - 在 do.call 调用的函数中查找局部变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66895588/