【发布时间】:2012-09-17 20:57:22
【问题描述】:
我正在用 R 开发一个包。我有一堆函数,其中一些需要一些全局变量。如何管理包中的全局变量?
我读过一些关于环境的东西,但我不明白它是如何工作的,如果这甚至是处理事情的方式。
【问题讨论】:
-
您能否详细说明您的具体情况?然后我们可以帮助您找到替代方案,最好...
标签: r global-variables
我正在用 R 开发一个包。我有一堆函数,其中一些需要一些全局变量。如何管理包中的全局变量?
我读过一些关于环境的东西,但我不明白它是如何工作的,如果这甚至是处理事情的方式。
【问题讨论】:
标签: r global-variables
您可以通过环境使用包局部变量。这些变量将可用于包中的多个函数,但用户无法(轻松)访问,并且不会干扰用户的工作空间。一个快速简单的例子是:
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()
【讨论】:
emptyenv(),以防止意外拾取搜索路径中较高的值:因此new.env(parent = emptyenv()),而不仅仅是new.env()。
assign('key', value, pkg.env) 而不是 pkg.env$key <- value,因为 pkg.env 通常是一个锁定的环境。
require 的源代码以及它所做的一切。但是require 的帮助页面说它不会重新加载已经加载的命名空间(详细信息下第一段的中间)。所以,我不认为每次都会创建一个新环境。
您可以设置option,例如
options("mypkg-myval"=3)
1+getOption("mypkg-myval")
[1] 4
【讨论】:
.Options 的配对列表中,位于 base 包中。
?options。
一般来说,全局变量是邪恶的。它们邪恶的根本原则是您希望尽量减少包中的互连。这些互连通常会导致函数产生副作用,即它不仅取决于输入参数的结果是什么,还取决于某些全局变量的值。尤其是当函数数量增加时,这可能很难正确进行调试。
R 中的全局变量见SO post。
编辑以回应您的评论: 另一种方法可能是将所需信息传递给需要它的函数。您可以创建一个包含此信息的新对象:
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)
我希望这能让事情更清楚。
【讨论】:
您还可以创建一个令牌列表并将其添加到 R/sysdata.rda 并使用usethis::use_data(..., internal = TRUE)。此文件中的数据是内部数据,但所有函数均可访问。如果您只希望 some 函数访问令牌,则会出现唯一的问题,这将更好地服务于:
list2env(..., envir = environment()) 将它们注入到它们的环境中。【讨论】:
如果您不介意向您的包添加依赖项,您可以使用来自homonym package 的R6 对象,正如@greg-snow 的答案中的cmets 所建议的那样。
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 package (v0.3.0) 于 2020-05-27 创建
【讨论】: