R 在子例程中创建变量并返回环境时"no visible binding for global variable"注意事项



我正在尝试向CRAN提交一个包。 我的函数很长,有几千行长。 我重写了它并将其分解为一个包装器("外部"(函数,该函数调用一组"内部"子函数(未导出(,这些子函数创建我想返回到包装器函数环境的对象。 我尝试使用 assign(( 函数或 list2env((,它做同样的事情,只是它接受一个列表作为参数并返回在列表中命名为命名元素的对象。 当我对我的包运行 R CMD 检查时,会触发"全局变量没有可见绑定"警告,因为许多变量是在子函数中创建并从这些函数中返回到环境中的,之后在包装器环境中使用,而没有在此环境中显式创建它们的实例。

我之前在网上看到过关于这个问题的问题。 其中一些专门处理ggplot,dplyr或子集或data.frame问题。 这是更普遍的。 一些在线参考提到使用 utils::globalVariables 函数 (https://github.com/r-lib/devtools/issues/1714( 首先将这些变量声明为全局变量,我稍后将创建这些变量。 论坛提到要么将它们放在单独的全局变量中。R scrip,或在我的包装器函数开头的函数调用中。 但这种解决方案作为"黑客"似乎存在争议。 另一种解决方案(同样"黑客",但我想没关系(只是在代码开头将所有这些变量初始化为 NULL。

我见过的另一种解决方案是基本上将所有这些对象存储为在包装函数中初始化的列表的成员,然后返回子函数的所有输出以追加或修改列表项。这样,我要创建的全局对象不是单独的单独对象,而是列表的一部分,因此没有问题。 但是,我需要单独重写我的代码以将每个对象引用为列表项(例如,tmp$obj 而不仅仅是 obj(。 另一方面,这在某种程度上会更简单,因为所有对象都存储在一个列表中,该列表可以作为单个单元引用和传递,而不必单独跟踪它们。

我想听听有经验的人关于这些方法的各种优点/缺点或正确性。

将对象返回到环境

outside_function <- function() {
k <- letters[17:23]
#inside_function creates objects m and z which did not exist before               
inside_function()
ls()
print(m)
print(z)
inside_function()
ls()
#z and m should now be overwritten
print(m)
print(z)
}
inside_function <- function() {
m <- matrix(runif(4), ncol=2)
z <- letters[1:10]
#assign to the wrapping environment 
assign("m", m, envir=parent.frame())
assign("z", z, envir=parent.frame())
#an equivalent way:
list2env(list(m=m, z=z), envir=parent.frame())  
}

替代方法,将对象保留为列表

outside_function <- function() {
k <- letters[17:23]
#inside_function creates objects m and z which did not exist before               
tmp <- inside_function()
#refer to m and z only as items in tmp
print(tmp$m)
print(tmp$z)
tmp <- inside_function()
ls()
#z and m should now be overwritten
print(tmp$m)
print(tmp$z)
}
inside_function <- function() {
m <- matrix(runif(4), ncol=2)
z <- letters[1:10]
#return as list items
list(m=m, z=z)
}

对于第一个,我得到以下注释:

outside_function: no visible binding for global variable 'm'
outside_function: no visible binding for global variable 'z'

我在构建的一个包中遇到了这个问题,它的唯一目的是将变量分配给环境。我感受到你的痛苦。

我的解决方案是将变量初始化为 NULL。此外,我不会真正称之为黑客,因为许多编程语言(我能想到的最简单的编程语言是Visual basic(要求您在使用变量之前对其进行初始化。列出并不是一个坏主意,但正如您所说,它需要大量的重构,并且可能不值得您花时间。

使用环境的解决方案

所以我想出了如何做到这一点。 是的,您可以使用列表方法,但它有点人为。 这是正确的方法:在包装函数outside_function中定义一个命名的空环境,所有要存储(并在末尾返回(的对象都写入该环境。 然后将此环境作为单个参数(如列表(传递给内部函数。 在inside_function中,您可以实时编辑存储的环境对象,而无需将列表中的对象显式返回到列表对象。 它更干净。

outside_function <- function() {

myenv <- new.env(parent = emptyenv())
#object k exists in local environment, but not myenv
k <- LETTERS[17:23]
#assign list of objects to 
print(ls()) #two objects, k and myenv
print(ls(myenv))
print("first run")
inside_function(env=myenv) 
print("LS")
print(as.list(myenv))
print("second run")
inside_function(env=myenv)
print("LS")
print(as.list(myenv))
#inside here, have to refer to objects as list elements
#the command print(m) searches through environments to find an object
#if nothing exists locally, m will find myenv$m, but is misleading
#try(print(m))  
#now create a local object m that is different
m <- "blah"
print(m) #gives 'blah'
print(myenv$m)

#return at end as a list
invisible(as.list(myenv))

}  
inside_function <- function(env) {
#create/overwrite objects in env

env$m <- matrix(stats::runif(4), ncol=2)
#these are created in real time within inside_function without having
#to return env (notice NULL is a returned value)
print(env$m)
#overwite
env$m <- matrix(stats::runif(4), ncol=2)
print(env$m)
env$d <- 5
print(env$d)
env$d <- env$d + runif(1)
env$z <- letters[sample(1:20, size=6)]
invisible(NULL)
}
tmp <- outside_function()
print(tmp) #contains all the objects as a list

最新更新