Source script to separate environment in R, not the global environment
The following environment insertion appears to achieve the desired functionality:
Check the current search path:
search()
# [1] ".GlobalEnv" "package:stats" "package:graphics"
# [4] "package:grDevices" "package:utils" "package:datasets"
# [7] "package:methods" "Autoloads" "package:base"
Add new environment for sourced packages and use local
parameter when source()
ing:
myEnv <- new.env()
source("some_other_script.R", local=myEnv)
attach(myEnv, name="sourced_scripts")
Check the search path:
search()
# [1] ".GlobalEnv" "sourced_scripts" "package:dplyr"
# [4] "package:stats" "package:graphics" "package:grDevices"
# [7] "package:utils" "package:datasets" "package:methods"
# [10] "Autoloads" "package:base"
Note that we attach()
the new environment after sourcing, so that dplyr
is attached after our script environment in the search path.
Separate scripts from .GlobalEnv: Source script that source scripts
You can use trace
to inject code in functions,
so you could force all source
calls to set local = TRUE
.
Here I just override it if local
is FALSE
in case any nested calls to source
actually set it to other environments due to special logic of their own.
env <- new.env()
# use !isTRUE if you want to support older R versions (<3.5.0)
tracer <- quote(
if (isFALSE(local)) {
local <- TRUE
}
)
trace(source, tracer, print = FALSE, where = .GlobalEnv)
# if you're doing this inside a function, uncomment the next line
#on.exit(untrace(source, where = .GlobalEnv))
source("main.R", local = env)
As mentioned in the code,
if you wrap this logic in a function,
consider using on.exit
to make sure you untrace
even if there are errors.
EDIT: as mentioned in the comments,
this could have issues if some of the scripts you will be loading assume there is 1 (global) environment where everything ends.
I suppose you could change the tracer to something like
tracer <- quote(
if (missing(local)) {
local <- TRUE
}
)
or maybe
tracer <- quote(
if (isFALSE(local)) {
# fetch the specific environment you created
local <- get("env", .GlobalEnv)
}
)
The former assumes that if the script didn't specify local
at all,
it doesn't care about which environment ends up holding everything.
The latter assumes that source
calls that didn't specify local
or set it to FALSE
want everything to end up in 1 environment,
and modify the logic to use your environment instead of the global one.
Is it possible to move a variable from the global environment into a separate environment?
Maybe:
library(purrr)
a <- 111
b <- 'hello'
my_envir <- new.env()
names(.GlobalEnv) %>%
walk(~ assign(.x, get(.x), envir = my_envir))
eapply(my_envir, function(x) x)
#> $my_envir
#> <environment: 0x7fed59e56dc8>
#>
#> $a
#> [1] 111
#>
#> $b
#> [1] "hello"
Or
library(purrr)
a <- 111
b <- 'hello'
my_envir <- new.env()
eapply(.GlobalEnv, function(x) x) %>%
discard(is.environment) %>%
{walk2(., names(.), ~{
assign(.y, .x, envir = my_envir)
exec('rm', .y, envir = .GlobalEnv)}
)}
eapply(my_envir, function(x) x)
#> $a
#> [1] 111
#>
#> $b
#> [1] "hello"
Created on 2021-12-31 by the reprex package (v2.0.1)
How to overwrite .GlobalEnv with other environment
1) To copy all elements of an environment to the global environment convert that environment to a list using as.list
and then copy the contents of that list to the global environment using list2env
. Whether you really need to do this or not is not clear. Could you not just leave the objects in that hidden environment?
list2env(as.list(myEnv), .GlobalEnv)
X
## [1] 42
2) I don't really recommend this but rather than modifying the global environment it is possible to place myEnv on the search path and then you can refer to its objects as long as they are not masked by objects of the same name in the global environment.
X <- 43
attach(myEnv)
X
## 43
get("X", "myEnv")
## 42
rm(X)
X
## 42
Note
Note that to create a hidden environment in your package you can just put this into the source code of the package and not export it.
myEnv <- new.env()
For example, the lattice package creates a hidden environment called .latticeEnv
in this source file: https://github.com/cran/lattice/blob/master/R/zzz.R
Cleaning up the global environment after sourcing: How to remove objects of a certain type in R
As an alternative approach (similar to @Ken's suggestion from the comments), the following code allows you to delete all objects created after a certain point, except one (or more) that you specify:
freeze <- ls() # all objects created after here will be deleted
var <- letters
for (i in var) {
assign(i, runif(1))
}
df <- data.frame(x1 = a, x2 = b, x3 = c)
rm(list = setdiff(ls(), c(freeze, "df"))) #delete old objects except df
The workhorse here is setdiff()
, which will return a list a list of the items that appear in the first list but not the second. In this case, all items created after freeze
except df
. As an added bonus, freeze
is deleted here as well.
Is it possible to attach package environments as parents of a specific environment, not the global environment, in R?
You have only one search path so there's no way to properly attach to another one.
You can still have a chain of parent environments though, we might redefine library
in your repl_env
to set up this chain
repl_env <- new.env()
with(repl_env, library <- function(package) {
# fetch repl_env from the inside
repl_env <- parent.env(environment())
# and its parent (.GlobalEnv the first time)
parent_env <- parent.env(repl_env)
# create a new env for our package and fill it
pkg_env <- new.env()
package <- deparse1(substitute(package))
object_nms <- getNamespaceExports(package)
objects <- mget(object_nms, envir = asNamespace(package))
list2env(objects, pkg_env)
# stitch it above repl_env and below repl_env's parent
parent.env(pkg_env) <- parent_env
parent.env(repl_env) <- pkg_env
# base::library returns the search path invisibly but here it woudn't make
# sense so we just return NULL
invisible(NULL)
})
simple_repl(repl_env)
>>>> x <- "hello"
>>>> y <- "world"
>>>> library(glue)
>>>> glue("{x} {y}")
#> hello world
>>>>
# the {glue} package is not on the search path
search()
#> [1] ".GlobalEnv" "tools:rstudio" "package:stats" "package:graphics"
#> [5] "package:grDevices" "package:utils" "package:datasets" "package:methods"
#> [9] "Autoloads" "package:base"
Use repl_env <- new.env(parent = parent.env(.GlobalEnv))
as your first line if you don't want to have access to the global environment's objects.
It will never be 100% robust however, it was a fun exercise but think carefully before doing something serious with this.
Related Topics
How to Extend Letters Past 26 Characters E.G., Aa, Ab, Ac...
How to Pass Multiple Arguments to a Function as a Single Vector
Warning in Install.Packages: Unable to Move Temporary Installation
How to Merge Two Data Frames on Common Columns in R with Sum of Others
Varying Axis Labels Formatter Per Facet in Ggplot/R
How to Italicize One Category in a Legend in Ggplot2
How to Calculate Number of Days Between Two Dates in R
Spacing Between Boxplots in Ggplot2
R - Ggplot Line Color (Using Geom_Line) Doesn't Change
In R, Using Scientific Notation 10^ Rather Than E+
Move Nas to the End of Each Column in a Data Frame
R: Find Vector in List of Vectors
When and Why Does "Print" Need Two Attempts to Print a "Data.Table"
Dplyr/Rlang: Parse_Expr with Multiple Expressions
Convert a Dataframe to an Object of Class "Dist" Without Actually Calculating Distances in R
Dplyr::N() Returns "Error: Error: N() Should Only Be Called in a Data Context "