R - How to find the environment where a function i

2019-08-04 18:47发布

问题:

I'm trying to create a function that references the scope from which it was called to create a unique ID:

uniqueid <- function(name, envir = NULL) {

  if(identical(envir, globalenv())) {
    e = envir
  } else if (is.null(envir)) {
    e = parent.frame()
  } else if (is.environment(envir)) {
    e = envir
  } else {
    stop("given non-default argument \'envir\' is not an environment")
  }

  return(paste(sep = "",
    sub('<environment: (.*)>', '\\1', capture.output(e)),
    ".",
    name
    )
  )
}

What can I do to make this work the way I think it should work? R keeps returning the scope in which the function was defined, and not where it is evaluated:

hello <- new.env()
hello$env <- new.env()

eval(envir=hello$env, {
  print(environment())
  print(hello$env) #hello$env is an environment
  print(hello) #hello is an environment
  uniqueid('hi')
})

I'm trying to get at least one of these to match up, but it doesn't seem like it wants to work. R either returns the global environment, or the ever-changing scope of a temporary frame/environment created for an instance of a call to the function. The ID needs to be reproducible between multiple calls and dependent on the environment.

I know I could pass the environment, but I'm beginning to wonder whether doing capture of the caller's environment is actually possible.

回答1:

TL,DR: parent.frame() not parent.frame(environment()); evalq not eval.

parent.frame takes argument n being number of generations to go back, not the result of an environment() call (which is an environment). If you leave it out of the parent.frame() call you're fine.

Also, yoru example code doesn't work as you expect, because your environment() command is being evaluated in the call to eval, before being evaluated as part of eval. I.e. you must quote your argument to eval. Alternatively, use evalq rather than eval.

E.g.

uniqueid <- function(name) {
  print(parent.frame())
}
hello <- new.env()
hello$env <- new.env()
evalq(envir=hello$env, {
  print(environment()) # prints hello$env
  print(hello$env)     # prints hello$env
  print(hello)         # prints hello
  uniqueid('hi')       # prints hello$env
})

E.g. this will get the environment ID and add it to the name:

uniqueid <- function(name) {
    # GlobalEnv etc have names; hello doesn't
    envName <- environmentName(parent.frame())
    # get the environment ID from the parent.frame() output if no name
    if (envName == "") {
        p <- parent.frame()
        envName <- sub('<environment: (.*)>', '\\1', capture.output(p))
    }
    return(paste(envName, name, sep=':'))
}
uniqueid('x') # R_GlobalEnv:x
print(hello) # <environment: 0x4641fa0>
evalq(envir=hello, uniqueid('x')) # "0x4641fa0:x"
print(hello$env) # <environment: 0x4640e60>
evalq(envir=hello$env, uniqueid('x')) # "0x4640e60:x"


回答2:

Your problem is that eval() expects something that is unevaluated. By passing in a code block {}, you are not preventing evaluation. For example, consider the simpler case

a<-10
ee<-new.env()
ee$a<-5

eval(envir=ee, a)
# [1] 10
eval(envir=ee, {a})
# [1] 10
eval(envir=ee, quote(a))
# [1] 5
evalq(envir=ee, a)
# [1] 5

Notice how the last two either explicitly quote the expression, or use evalq() to do the quoting of the expression. These allow the evaluation to be delayed until executed inside the requested environment.

So if you just change your eval() to evalq(), you should be fine.



标签: r scope