【问题标题】:Separate scripts from .GlobalEnv: Source script that source scripts从 .GlobalEnv 中分离脚本:源脚本的源脚本
【发布时间】:2021-11-16 20:57:14
【问题描述】:

这个问题类似于Source script to separate environment in R, not the global environment,但有一个关键的转折。

考虑一个引用另一个脚本的脚本:

# main.R
source("funs.R")
x <- 1
# funs.R
hello <- function() {message("Hi")}

我想获取脚本main.R 并将所有内容保存在“本地”环境中,例如env &lt;- new.env()。通常,可以调用source("main.R", local = env) 并期望一切都在env 环境中。但是,这里不是这样:xenv 的一部分,但函数hello 不是!它位于.GlobalEnv

问题:如何在 R 中将脚本提供给单独的环境,即使该脚本本身提供其他脚本,并且不修改其他正在提供的脚本?

感谢您的帮助,如果我能澄清任何事情,请告诉我。

编辑 1:更新问题,明确指出不能修改作为源的脚本(假设它们不受您的控制)。

【问题讨论】:

  • 虽然你不能修改源脚本,但你可以阅读它们..所以你不能将它们作为字符串阅读,修改字符串,然后执行它们..(例如使用 eval 和解析..)?它应该至少适用于第一级脚本..但是你有几个级别的源脚本吗?
  • 一般来说,修复源代码(例如,通过使其成为一个包)(必要时进行控制)是最好的,其次是隔离,以防您不想这样做。在某些情况下,可能会有更简单的解决方案——但通常不是这样。这听起来像是一个 xy 问题,但您需要提供更多背景才能解决它。

标签: r r-environment


【解决方案1】:

您可以使用trace 在函数中注入代码, 所以你可以强制所有source 调用设置local = TRUE。 如果localFALSE,我在这里只覆盖它,以防任何对source 的嵌套调用实际上由于它们自己的特殊逻辑而将其设置为其他环境。

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)

如代码中所述, 如果您将此逻辑包装在一个函数中, 考虑使用on.exit 来确保您untrace 即使有错误。

编辑:如 cmets 中所述, 如果您将加载的某些脚本假设有 1 个(全局)环境,一切都结束了,这可能会出现问题。 我想您可以将跟踪器更改为类似

tracer <- quote(
  if (missing(local)) {
    local <- TRUE
  }
)

或许

tracer <- quote(
  if (isFALSE(local)) {
    # fetch the specific environment you created
    local <- get("env", .GlobalEnv)
  }
)

前者假设如果脚本根本没有指定local, 它不关心哪个环境最终容纳了所有东西。 后者假定未指定 local 或将其设置为 FALSEsource 调用希望所有内容都在 1 个环境中结束, 并修改逻辑以使用您的环境而不是全局环境。

【讨论】:

  • 感谢您的回答,非常有趣的方法。您能否阐明tracer 中的local 是如何被source 函数捕获的?
  • 如果源脚本依赖global执行,这种方法会导致问题。
  • @GuilhermeSalomé 你可以想象quoted 代码在你跟踪的函数的开头被逐字注入,所以它本质上就像你修改了源代码本身一样,你可以操作任何东西相应地在函数调用内部。
  • @jan-glx 是的,尽管我添加了潜在的替代方案,但它对于任何情况都不是真正的防弹。
  • @jan-glx 这是一个很好的呼吁。你有一个最小的例子可以分享吗?
【解决方案2】:

保护自己免受无法控制的代码副作用的最佳方法是隔离。您可以使用callr 轻松执行在单独的 R 会话中隔离的脚本:

使用环境:

env <- new.env()
env <- as.environment(callr::r(function(env) {
    list2env(env, .GlobalEnv)
    source("main.R")
    as.list(.GlobalEnv)
}, args = list(as.list(env))))
env
#> <environment: 0x0000000018124878>
env$hello()
#> Hi

坚持列表的更简单版本:

params <- list()
results <- callr::r(function(params) {
    list2env(params, .GlobalEnv)
    source("main.R")
    as.list(.GlobalEnv)
}, args = list(params))
results
#> $x
#> [1] 1
#> 
#> $hello
#> function () 
#> {
#>   message("Hi")
#> }
results$hello()
#> Hi

param 部分仅在您确实需要提供输入脚本时才需要(不用于您的示例)。 显然,这不适用于开放连接和类似的东西。在这种情况下,您可能需要查看callr::r_session

【讨论】:

  • 感谢您的回答。一个有趣的方法,我以前不知道这个包。不幸的是,该软件包似乎有一个很大的缺点:Note that the arguments will be serialized and saved to a file, so if they are large R objects, it might take a long time for the child process to start up. 这对我来说是一个交易破坏者,但在其他情况下肯定有用。
  • 是的,使用 R 共享内存并不是那么容易,但是您可以通过将临时文件仅放在 RAM 中来减轻负担(通过设置 TMPDIR=/run/user/you 或任何有 ramdisk 的地方)
【解决方案3】:

免责声明:非常丑陋且有潜在危险,但无论如何。

重新定义source:

env<-new.env()
source<-function(...) base::source(..., local = env)
source("main.R")
#just remove your redefinition when you don't need it
rm(source)

【讨论】:

  • 我打算建议这个,但是:source &lt;- function(...,local=NULL) base::source(...,local=env) 以防万一某些源文件已经包含 local= 参数
  • 感谢您的回答。这是一种有趣的方法。我赞成 Alexis 上面的回答,因为它不需要重写基本函数。
【解决方案4】:

在所有嵌套的source() 函数中使用参数local = TRUE

文件 3:

# last.R
y <- 2

文件 2:

# funs.R
source("last.R", local = TRUE)
hello <- function() {message("Hi")}

文件 1:

# main.R
source("funs.R", local = TRUE)
x <- 1

采购文件 1:

env <- new.env()
source("main.R", local = env)

检查环境中的内容:

as.list(env)

$x
[1] 1

$y
[1] 2

$hello
function() {message("Hi")}
<environment: 0x000001d9dff719b8>

【讨论】:

  • 感谢您的回答。我试图避免修改对source 的嵌套调用,因为这些调用可能并不总是在我的控制之下。我将编辑问题以明确这一点。
  • 我不知道有一种方法可以在不依赖函数的 local 参数的情况下做你想做的事。因此,如果您无法更改脚本,我可以谨慎建议的唯一另一件事是暂时覆盖此参数的默认值,以便在您的脚本中调用它时默认为TRUE 而不是FALSE。祈祷有人可以为您建议一种替代的、更安全的方法。
  • 这是一个很好的观点。我曾希望 source 在 Python 中或多或少像 import 那样工作,但事实并非如此。无论如何感谢您的回答!
猜你喜欢
  • 2010-09-08
  • 2021-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多