67

我正在用 R 开发一个包。我有一堆函数,其中一些需要一些全局变量。如何管理包中的全局变量?

我读过一些关于环境的东西,但我不明白它是如何工作的,如果这甚至是处理这些事情的方式。

4

6 回答 6

69

您可以通过环境使用包局部变量。这些变量将可用于包中的多个函数,但用户无法(轻松)访问,并且不会干扰用户的工作空间。一个快速简单的例子是:

pkg.env <- new.env()

pkg.env$cur.val <- 0
pkg.env$times.changed <- 0

inc <- function(by=1) {
    pkg.env$times.changed <- pkg.env$times.changed + 1
    pkg.env$cur.val <- pkg.env$cur.val + by
    pkg.env$cur.val
}

dec <- function(by=1) {
    pkg.env$times.changed <- pkg.env$times.changed + 1
    pkg.env$cur.val <- pkg.env$cur.val - by
    pkg.env$cur.val
}

cur <- function(){
    cat('the current value is', pkg.env$cur.val, 'and it has been changed', 
        pkg.env$times.changed, 'times\n')
}

inc()
inc()
inc(5)
dec()
dec(2)
inc()
cur()
于 2012-09-26T15:53:28.793 回答
21

你可以设置一个option,例如

options("mypkg-myval"=3)
1+getOption("mypkg-myval")
[1] 4
于 2012-09-26T10:39:34.390 回答
16

一般来说,全局变量是邪恶的。它们邪恶的根本原理是您希望尽量减少包中的互连。这些互连通常会导致函数产生副作用,即它不仅取决于输入参数的结果是什么,还取决于某些全局变量的值。尤其是当函数的数量增加时,这可能很难正确进行调试。

对于 R 中的全局变量,请参阅此SO 帖子

根据您的评论进行编辑: 另一种方法是将所需信息传递给需要它的功能。您可以创建一个包含此信息的新对象:

token_information = list(token1 = "087091287129387",
                         token2 = "UA2329723")

并要求所有需要此信息的函数将其作为参数:

do_stuff = function(arg1, arg2, token)
do_stuff(arg1, arg2, token = token_information)

这样从代码中就可以看出函数中需要token信息,可以自行调试函数。此外,该函数没有副作用,因为它的行为完全由它的输入参数决定。典型的用户脚本如下所示:

token_info = create_token(token1, token2)
do_stuff(arg1, arg2, token_info)

我希望这能让事情更清楚。

于 2012-09-26T09:29:50.580 回答
3

问题不清楚:

  • 一个 R 进程还是几个?

  • 只是在一台主机上,还是在多台机器上?

  • 它们之间是否存在共同的文件访问权限?

在复杂性增加的顺序中,我将使用一个文件、一个通过RSQlite包的 SQLite 后端或(我最喜欢的 :) rredis包来设置/读取 Redis 实例。

于 2012-09-26T11:47:41.547 回答
2

您还可以创建一个令牌列表并将其添加到 R/sysdata.rda 中usethis::use_data(..., internal = TRUE)。此文件中的数据是内部数据,但所有函数均可访问。如果您只希望某些功能访问令牌,则会出现唯一的问题,这将更好地服务于:

  1. 上面已经提出的环境解决方案;或者
  2. 创建一个隐藏的辅助函数来保存令牌并返回它们。然后只需在使用令牌的函数中调用这个隐藏函数,并且(假设它是一个列表)你可以将它们注入到它们的环境中list2env(..., envir = environment())
于 2019-09-03T16:33:59.533 回答
1

如果您不介意向您的包添加依赖项,您可以使用同名包R6中的一个对象,正如@greg-snow 答案的评论中所建议的那样。

R6对象是可以添加公共和私有方法的实际环境,非常轻量级,并且可能是共享包的全局变量的良好且更严格的选择,而不会污染全局环境。

与@greg-snow 的解决方案相比,它允许对变量进行更严格的控制(例如,您可以添加检查类型的方法)。缺点可能是依赖关系,当然还有学习R6语法。

library(R6)
MyPkgOptions = R6::R6Class(
  "mypkg_options",
  public = list(
    get_option = function(x) private$.options[[x]]
  ),
  active = list(
    var1 = function(x){
      if(missing(x)) private$.options[['var1']]
      else stop("This is an environment parameter that cannot be changed")
    }
    ,var2 = function(x){
      if(missing(x)) private$.options[['var2']]
      else stop("This is an environment parameter that cannot be changed")
    }
  ),
  private = list(
    .options = list(
      var1 = 1,
      var2 = 2
    )
  )
)
# Create an instance
mypkg_options = MyPkgOptions$new()
# Fetch values from active fields
mypkg_options$var1
#> [1] 1
mypkg_options$var2
#> [1] 2
# Alternative way
mypkg_options$get_option("var1")
#> [1] 1
mypkg_options$get_option("var3")
#> NULL
# Variables are locked unless you add a method to change them
mypkg_options$var1 = 3
#> Error in (function (x) : This is an environment parameter that cannot be changed

reprex 包(v0.3.0)于 2020-05-27 创建

于 2020-05-27T13:13:02.040 回答