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

R "no visible binding for global variable" note when creating variables in sub-routines and returning to environment

我正在尝试向 CRAN 提交包裹。我的函数很长,有几千行。我重写了它并将其分解为一个包装器 ("outside") 函数,该函数调用一组 "inside" 子函数(未导出),这些子函数创建了我想要 return 到包装器的对象功能环境。我尝试过使用 assign() 函数或 list2env(),除了它以列表作为参数并将 returns 对象命名为其在列表中的命名元素外,它们做同样的事情。当我 运行 R CMD 检查我的包时,触发了 "no visible binding for global variables" 警告,因为在子函数中创建了许多变量并从这些函数中 returned 到环境中,并且之后在包装器环境中使用,而没有在此环境中创建它们的显式实例。

之前在网上看到过关于这个问题的提问。其中一些专门处理 ggplot、dplyr 或子集或 data.frame 问题。这个比较通用。一些在线参考资料提到使用 utils::globalVariables 函数 (https://github.com/r-lib/devtools/issues/1714) 首先声明这些我将在稍后创建的变量作为全局变量。论坛提到要么将它们放在单独的 globals.R 脚本中,要么在我的包装函数开头的函数调用中。但是这个解决方案作为一个"hack"似乎是有争议的。另一个解决方案(等于 "hackish",但我想还好)只是在代码开头将所有这些变量初始化为 NULL。

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

我想听听有经验的人对这些方法的各种 advantages/disadvantages 或正确性的看法。

将对象返回到环境

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。此外,我不会真正称之为 hackish,因为大量编程语言(我能想到的最简单的是 visual basic)要求您在使用变量之前对其进行初始化。上市不是一个坏主意,但正如您所说,它需要大量重构,可能不值得您花时间。

使用环境的解决方案

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

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