apply() and forceAndCall() ignoring get() from parent.frame()

女生的网名这么多〃 提交于 2020-01-13 05:39:13

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!