问题
I don't understand why this code is failing on the last printed line:
f <- function(x) get('v', envir = parent.frame(), inherits = TRUE)
run <- function() {
v <- 'test variable'
print(f())
print((function() f())())
print(apply(X = data.frame(1:2), MARGIN = 1, f))
}
run()
with:
Error in get("v", envir = parent.frame(), inherits = TRUE) :
object 'v' not found
The first print statement shows that v
is found in parent.frame(1)
. The second print statement shows that v
is found in parent.frame(2)
due to inherits = TRUE
. The last one is the enigma.
Seems that apply
is ignoring the get(envir = parent.frame())
. I traced this back to function forceAndCall()
within apply()
. Am I missing something or is this a bug?
In real application, v
is only defined in the calling environment of the function f
(i.e. in parent.frame()
or above), but never in parent.env()
.
回答1:
Not relay a solution but it avoids the error.
The manual of parent.frame
say: convenient shorthand for sys.frame(sys.parent(n))
. Using sys.frame(sys.parent(n))
results in the same error. But when I ask for sys.parents()
and take the one before the last the error disappears or simply use dynGet
as found out by @DavorJosipovic.
#f <- function(x) get('v', envir = sys.frame(max(0, sys.parents()[length(sys.parents())-1])), inherits = TRUE)
f <- function(x) dynGet('v', inherits = TRUE)
run <- function() {
v <- 'test variable'
print(f())
print((function() f())())
print(apply(X = data.frame(1:2), MARGIN = 1, f))
}
run()
#[1] "test variable"
#[1] "test variable"
#[1] "test variable" "test variable"
回答2:
The key issue here are the calling environments and the way get(inherits=TRUE)
handles them. For starters, I suggest one reads Hadley's Function environments.
More specifically, one has to understand function's enclosing and calling environments to understand what is going on.
v
is defined in the function f
calling environment, which is -- looking from the function's execution environment -- parent.frame(1L)
in the first print statement, and parent.frame(2L)
in the last two print statements.
I thought that if I supply envir=parent.frame(1L)
with inherits=TRUE
, the get
function would traverse the parent.frame()
hierarchy and eventually find v
in one of the parent calling environments. It does not. In reality it starts from the envir
environment one supplies and looks in the parent enclosing environments for v
. For the second print statement, the enclosing parent environment of parent.frame(1L)
is exactly where v
is defined, but in the last print statement, the enclosing parent environment of parent.frame(1L)
is the base
namespace where apply
is defined. Enclosing parent environment on top of that is the global environment. So v
is not found.
What we really need is a get
function that doesn't search in the parent enclosing environments hierarchy for v
, but in parent calling environments hierarchy. And that is exactly what dynget()
does.
dynGet() is somewhat experimental and to be used inside another function. It looks for an object in the callers, i.e., the sys.frame()s of the function. Use with caution
This function will traverse the parent calling environments sys.parents()
with sys.frame()
and look for v
in each of them. So f <- function(x) dynGet('v', inherits = TRUE)
solves the problem.
回答3:
As @Davor said, invoking apply
adds another layer of parent.frame() which leads to a new environment path, here is one way to search in two different environments.
f <- function(x) {
cat('f Env.')
print(environment())
#browser()
if(is.null(get0('v', parent.frame()))) {
get('v', envir = parent.frame(2), inherits = TRUE)} else {
get('v', envir = parent.frame(1), inherits = TRUE)}
}
run <- function() {
cat('run Env.')
print(environment())
v <- 'test variable'
print(f())
print((function() f())())
print(apply(X = data.frame(1:2), MARGIN = 1, f))
}
run()
If we un-comment browser()
in f
, comment the 1st and 2nd print
in run
and rerun the code we can see that parent.frame()
and parent.frame(2)
have different parent.env
回答4:
I'm not sure this fully answers the question, and I don't fully understand how the namespaces/environments are resolved. But consider this extension to your code, where I've defined my own "apply" functions. One defined in the calling run
function, one defined outside:
X <- data.frame(1:2)
my_apply1 <- function(X, FUN) {
for (i in seq_along(X)) {
print(FUN(X[[i]]))
}
}
run <- function() {
f <- function(x) get('v', envir = parent.frame(), inherits = TRUE)
v <- 'test variable'
print(f())
g <- function() { f() }; print(g())
my_apply2 <- function(X, FUN) {
for (i in seq_along(X)) {
print(FUN(X[[i]]))
}
}
my_apply2(X, f)
my_apply1(X, f)
#print(my_apply(matrix(1:6, ncol=2), MARGIN = 1, f))
}
run()
[1] "test variable" [1] "test variable" [1] "test variable" Error in get("v", envir = parent.frame(), inherits = TRUE) : object 'v' not found
This indicates to me, that the environment inheritance does not follow the same path as the nesting functions, in the sense that the parsing occours as
run() => f() => base::apply() => f() => where is v?
whereas finding the variable v
follows back where the functions are defined
where is v? => f() => base::apply => base => v?
来源:https://stackoverflow.com/questions/57672803/apply-and-forceandcall-ignoring-get-from-parent-frame