Reputation: 69
Given a function creating several objects in its temporary environment:
myFun <- function(arg1 = "hey", arg2 = "bye") {
x <- paste(arg1, arg2)
y <- paste(x, "hey")
z <- paste(y, "again!")
return(z)
}
Is there a function that can extract the temporary environment of another function ? e.g.
myFun_env <- capture_env(myFun())
That would return as output the equivalent to:
myFun_env <- rlang::env(
arg1 = "hey",
arg2 = "bye",
x = paste(arg1, arg2),
y = paste(x, "hey"),
z = paste(y, "again!")
)
> rlang::env_print(myFun_env)
<environment: 0x556959d73708>
parent: <environment: global>
bindings:
* x: <chr>
* y: <chr>
* z: <chr>
* arg1: <chr>
* arg2: <chr>
Update 1: Solution from user G. Grothendieck https://stackoverflow.com/users/516548/g-grothendieck
> trace(myFun, quote(assign(".Env", environment(), .GlobalEnv)), print = FALSE)
> myFun()
> rlang::env_print(.Env)
<environment: 0x556bf1a8ec08>
parent: <environment: global>
bindings:
* z: <chr>
* y: <chr>
* x: <chr>
* arg1: <chr>
* arg2: <chr>
> .Env$arg1
[1] "hey"
This modifies the function's body, but can be removed via untrace:
> body(myFun)
{
.doTrace(assign(".Env", environment(), .GlobalEnv))
{
x <- paste(arg1, arg2)
y <- paste(x, "hey")
z <- paste(y, "again!")
return(z)
}
}
> untrace(myFun)
> body(myFun)
{
x <- paste(arg1, arg2)
y <- paste(x, "hey")
z <- paste(y, "again!")
return(z)
}
>
Update 2: Wrapping things up, we could have a function that captures another function's environment, returns it and writes it to disk for further loading:
captureAndSave_functionEnv <- function (myFun, ...) {
trace(myFun, quote(assign(".myFun_env", environment(), .GlobalEnv)), print = FALSE)
#trace(myFun, quote({ untrace(myFun); assign(".myFun_env", environment(), parent.frame()) }), print = FALSE)
on.exit(untrace(myFun))
myFun(...)
saveRDS(.myFun_env, paste0(deparse(substitute(myFun)), ".rds"))
return(.myFun_env)
}
> captureAndSave_functionEnv(myFun)
<environment: 0x558f877eff20>
> myFun_env_loaded <- readRDS("myFun.rds")
> rlang::env_print(myFun_env_loaded)
<environment: 0x558f86d00890>
parent: <environment: global>
bindings:
* z: <chr>
* y: <chr>
* x: <chr>
* arg1: <chr>
* arg2: <chr>
> myFun_env_loaded$arg1
[1] "hey"
Upvotes: 1
Views: 340
Reputation: 269556
Use trace to inject a statement that captures the environment as a side effect:
trace(myFun, quote(assign(".Env", environment(), .GlobalEnv)), print = FALSE)
myFun()
## [1] "hey bye hey again!"
ls(.Env)
## [1] "arg1" "arg2" "x" "y" "z"
Upvotes: 2
Reputation: 545588
After the function is called, the function’s call environment (its “stack frame”) no longer exists, unless the lifetime of that frame was somehow extended, by being referenced from somewhere.1
So, no, there’s no way to get the environment after the fact from outside. What you can do is modify the function:
myFun <- function(arg1 = "hey", arg2 = "bye") {
x <- paste(arg1, arg2)
y <- paste(x, "hey")
z <- paste(y, "again!")
environment()
}
Here we return the call frame instead of z
.
1 If this isn’t done explicitly, the environment is generally not referenced anywhere. However, there are exceptions: both functions and formulas by default reference the environment they were defined in. As a consequence, if you define a function or formula inside myFun()
and return that, you can retrieve the call frame of a myFun(…)
invocation by calling environment()
on the return value.
Upvotes: 2
Reputation: 57686
Yes, call the environment
function inside your function.
f <- function()
{
x <- 42
environment()
}
r$> f()
<environment: 0x0000020a1302a5a8>
r$> f()$x
[1] 42
Upvotes: 1