【问题标题】:R force local scopeR强制本地范围
【发布时间】:2013-02-27 22:28:51
【问题描述】:

这可能不是正确的术语,但希望我能明白我的意思。

我经常会做这样的事情:

myVar = 1
f <- function(myvar) { return(myVar); }
# f(2) = 1 now

R 很乐意在函数范围之外使用变量,这让我摸不着头脑,想知道我怎么可能得到我现在的结果。

是否有任何选项说“强制我只使用之前已在此函数范围内赋值的变量”?例如,Perl 的use strict 就是这样做的。但我不知道 R 是否有 my 的等价物。


编辑:谢谢,我知道我用不同的方式大写它们。确实,这个例子就是专门用来说明这个问题的!

我想知道当我这样做时 R 是否可以自动警告我。

编辑 2:另外,如果 Rkward 或其他 IDE 提供此功能,我也想知道。

【问题讨论】:

  • 只是澄清一下:您最初的问题似乎涉及强制本地范围,但您的编辑和答案涉及代码检查(静态分析)。你真正想要的是哪个?静态检查得到了解答,但局部变量的强制似乎没有得到解答。
  • @Xodarap - 你在下面得到了很多很好的答案,但我认为我的答案有几个有用的解决方案 - 即使我迟到了 ;-)
  • 关于 EDIT2:RStudio IDE 将“警告”您符号“myVar”不在范围内,即使询问您是否指的是“myvar”

标签: r


【解决方案1】:

@Tommy 给出了一个很好的答案,我用它创建了 3 个我认为在实践中更方便的函数。

严格

要使函数严格,你只需要调用

strict(f,x,y)

而不是

f(x,y)

示例:

my_fun1 <- function(a,b,c){a+b+c}
my_fun2 <- function(a,b,c){a+B+c}
B <- 1
my_fun1(1,2,3)        # 6
strict(my_fun1,1,2,3) # 6
my_fun2(1,2,3)        # 5
strict(my_fun2,1,2,3) # Error in (function (a, b, c)  : object 'B' not found

checkStrict1

要获得诊断,请使用可选的布尔参数执行 checkStrict1(f) 以显示更多或更少。

checkStrict1("my_fun1") # nothing
checkStrict1("my_fun2") # my_fun2  : B

一个更复杂的案例:

A <- 1 # unambiguous variable defined OUTSIDE AND INSIDE my_fun3
# B unambiguous variable defined only INSIDE my_fun3
C <- 1 # defined OUTSIDE AND INSIDE with ambiguous name (C is also a base function)
D <- 1 # defined only OUTSIDE my_fun3 (D is also a base function)
E <- 1 # unambiguous variable defined only OUTSIDE my_fun3
# G unambiguous variable defined only INSIDE my_fun3
# H is undeclared and doesn't exist at all
# I is undeclared (though I is also base function)
# v defined only INSIDE (v is also a base function)
my_fun3 <- function(a,b,c){
  A<-1;B<-1;C<-1;G<-1
  a+b+A+B+C+D+E+G+H+I+v+ my_fun1(1,2,3)
}
checkStrict1("my_fun3",show_global_functions = TRUE ,show_ambiguous = TRUE , show_inexistent = TRUE)

# my_fun3  : E 
# my_fun3  Ambiguous : D 
# my_fun3  Inexistent : H 
# my_fun3  Global functions : my_fun1

我选择在 3 个可选添加中默认显示不存在。您可以在函数定义中轻松更改它。

checkStrictAll

使用相同的参数诊断所有可能有问题的功能。

checkStrictAll()
my_fun2         : B 
my_fun3         : E 
my_fun3         Inexistent : H

来源

strict <- function(f1,...){
  function_text <- deparse(f1)
  function_text <- paste(function_text[1],function_text[2],paste(function_text[c(-1,-2,-length(function_text))],collapse=";"),"}",collapse="") 
  strict0 <- function(f1, pos=2) eval(substitute(f1), as.environment(pos))
  f1 <- eval(parse(text=paste0("strict0(",function_text,")")))
  do.call(f1,list(...))
}

checkStrict1 <- function(f_str,exceptions = NULL,n_char = nchar(f_str),show_global_functions = FALSE,show_ambiguous = FALSE, show_inexistent = TRUE){
  functions <-  c(lsf.str(envir=globalenv()))
  f <- try(eval(parse(text=f_str)),silent=TRUE)
  if(inherits(f, "try-error")) {return(NULL)}
  vars <- codetools::findGlobals(f)
  vars <- vars[!vars %in% exceptions]
  global_functions <- vars %in% functions

  in_global_env <- vapply(vars, exists, logical(1), envir=globalenv())
  in_local_env  <- vapply(vars, exists, logical(1), envir=as.environment(2))
  in_global_env_but_not_function <- rep(FALSE,length(vars))
  for (my_mode in c("logical", "integer", "double", "complex", "character", "raw","list", "NULL")){
    in_global_env_but_not_function <- in_global_env_but_not_function | vapply(vars, exists, logical(1), envir=globalenv(),mode = my_mode)
  }
  found     <- in_global_env_but_not_function & !in_local_env
  ambiguous <- in_global_env_but_not_function & in_local_env
  inexistent <- (!in_local_env) & (!in_global_env)
  if(typeof(f)=="closure"){
    if(any(found))           {cat(paste(f_str,paste(rep(" ",n_char-nchar(f_str)),collapse=""),":",                  paste(names(found)[found], collapse=', '),"\n"))}
    if(show_ambiguous        & any(ambiguous))       {cat(paste(f_str,paste(rep(" ",n_char-nchar(f_str)),collapse=""),"Ambiguous :",        paste(names(found)[ambiguous], collapse=', '),"\n"))}
    if(show_inexistent       & any(inexistent))      {cat(paste(f_str,paste(rep(" ",n_char-nchar(f_str)),collapse=""),"Inexistent :",       paste(names(found)[inexistent], collapse=', '),"\n"))}
    if(show_global_functions & any(global_functions)){cat(paste(f_str,paste(rep(" ",n_char-nchar(f_str)),collapse=""),"Global functions :", paste(names(found)[global_functions], collapse=', '),"\n"))}
    return(invisible(FALSE)) 
  } else {return(invisible(TRUE))}
}

checkStrictAll <-  function(exceptions = NULL,show_global_functions = FALSE,show_ambiguous = FALSE, show_inexistent = TRUE){
  functions <-  c(lsf.str(envir=globalenv()))
  n_char <- max(nchar(functions))  
  invisible(sapply(functions,checkStrict1,exceptions,n_char = n_char,show_global_functions,show_ambiguous, show_inexistent))
}

【讨论】:

    【解决方案2】:

    在 CRAN 上有一个新的包 modules 可以解决这个常见问题(请参阅小插图 here)。使用modules,函数会引发错误,而不是静默返回错误结果。

    # without modules
    myVar <- 1
    f <- function(myvar) { return(myVar) }
    f(2)
    [1] 1
    
    # with modules
    library(modules)
    m <- module({
      f <- function(myvar) { return(myVar) }
    })
    m$f(2)
    Error in m$f(2) : object 'myVar' not found
    

    这是我第一次使用它。这似乎很简单,因此我可能会将其包含在我的常规工作流程中,以防止耗时的事故。

    【讨论】:

      【解决方案3】:

      根据@c-urchin 的回答,对我有用的是定义一个脚本来读取我的所有函数,然后排除全局环境:

      filenames <- Sys.glob('fun/*.R')
      for (filename in filenames) {
          source(filename, local=T)
          funname <- sub('^fun/(.*).R$', "\\1", filename)
          eval(parse(text=paste('environment(',funname,') <- parent.env(globalenv())',sep='')))
      }
      

      我认为

      • 所有函数都包含在相对目录./fun
      • 每个.R 文件只包含一个与文件同名的函数。

      问题是,如果我的一个函数调用另一个函数,那么外部函数也必须先调用这个脚本,并且必须使用local=T 调用它:

      source('readfun.R', local=T)
      

      当然假设脚本文件被称为readfun.R

      【讨论】:

        【解决方案4】:

        使用get(x, inherits=FALSE) 将强制使用本地范围。

         myVar = 1
        
         f2 <- function(myvar) get("myVar", inherits=FALSE)
        
        
        f3 <- function(myvar){
         myVar <- myvar
         get("myVar", inherits=FALSE)
        }
        

        输出:

        > f2(8)    
        Error in get("myVar", inherits = FALSE) : object 'myVar' not found
        > f3(8)
        [1] 8
        

        【讨论】:

          【解决方案5】:

          你当然做错了。不要指望静态代码检查工具能找到你所有的错误。用测试检查你的代码。还有更多的测试。任何为在干净的环境中运行而编写的体面测试都会发现这种错误。为您的函数编写测试并使用它们。看看 CRAN 上的 testthat 包的荣耀。

          【讨论】:

          • 或者 RUnit 包。
          • ...但不要期望您的测试也能找到所有错误!使用所有工具供您使用——静态检查、单元测试以及像用户一样实际运行代码:)。然后准备好在 REAL 用户最终掌握它时修复更多错误。
          【解决方案6】:

          据我所知,R 不提供“使用严格”模式。所以你有两个选择:

          1 - 确保您所有的“严格”函数都没有 globalenv 作为环境。你可以为此定义一个不错的包装函数,但最简单的方法是调用local

          # Use "local" directly to control the function environment
          f <- local( function(myvar) { return(myVar); }, as.environment(2))
          f(3) # Error in f(3) : object 'myVar' not found
          
          # Create a wrapper function "strict" to do it for you...
          strict <- function(f, pos=2) eval(substitute(f), as.environment(pos))
          f <- strict( function(myvar) { return(myVar); } )
          f(3) # Error in f(3) : object 'myVar' not found
          

          2 - 进行代码分析,警告您“错误”的使用。

          这是一个函数checkStrict,希望能满足您的需求。它使用了优秀的codetools 包。

          # Checks a function for use of global variables
          # Returns TRUE if ok, FALSE if globals were found.
          checkStrict <- function(f, silent=FALSE) {
              vars <- codetools::findGlobals(f)
              found <- !vapply(vars, exists, logical(1), envir=as.environment(2))
              if (!silent && any(found)) {
                  warning("global variables used: ", paste(names(found)[found], collapse=', '))
                  return(invisible(FALSE))
              }
          
              !any(found)
          }
          

          并尝试一下:

          > myVar = 1
          > f <- function(myvar) { return(myVar); }
          > checkStrict(f)
          Warning message:
          In checkStrict(f) : global variables used: myVar
          

          【讨论】:

            【解决方案7】:

            environment(fun) = parent.env(environment(fun))

            将从您的搜索路径中删除“工作区”,保留其他所有内容。这可能最接近您想要的。

            【讨论】:

            • 如果您的函数加载库或执行任何其他更新searchpath 的操作,这将不起作用,因为新环境插入在函数的 env 和.GlobalEnv 之间。见stackoverflow.com/a/45893738/538603
            【解决方案8】:

            codetools 包中的checkUsage 很有帮助,但不会让你一路走好。 在未定义 myVar 的干净会话中,

            f <- function(myvar) { return(myVar); }
            codetools::checkUsage(f)
            

            给予

            <anonymous>: no visible binding for global variable ‘myVar’
            

            但是一旦你定义了myVarcheckUsage 就会很开心。

            请参阅 codetools 包中的 ?codetools:其中的某些内容可能有用:

            > findGlobals(f)
            [1] "{"      "myVar"  "return"
            > findLocals(f)
            character(0)
            

            【讨论】:

            • 谢谢 Ben,我想 checkUsage 不是我想要的。
            • @BenBolker +1 - 谢谢 Ben,之前没有看过这个包。在我的回答中,我设法使用findGlobals 来解决问题......
            【解决方案9】:

            您可以像这样动态更改环境树:

            a <- 1
            
            f <- function(){
                b <- 1
                print(b)
                print(a)
            }
            
            environment(f) <- new.env(parent = baseenv())
            
            f()
            

            f里面,可以找到b,而a找不到。

            但可能弊大于利。

            【讨论】:

            • 将父级设置为 baseenv 有点限制 - 你不能调用像 runif 这样的统计函数。我的回答略有不同(我不敢说“更好”;-)。
            【解决方案10】:

            您需要更正拼写错误:myvar != myVar。然后一切都会好起来的......

            范围解析是“由内而外”,从当前的开始,然后是封闭的,依此类推。

            编辑既然你已经澄清了你的问题,请查看包 codetools(它是 R Base 集的一部分):

            R> library(codetools)
            R> f <- function(myVAR) { return(myvar) }
            R> checkUsage(f)
            <anonymous>: no visible binding for global variable 'myvar'
            R> 
            

            【讨论】:

            • 谢谢,我知道这是问题所在。我在问 R 是否有一种自动方法来检测这种情况何时发生(即当我在其范围之外的函数中使用变量时)。
            • 如果myvar已经在全局环境中定义了,这个解决方案就不起作用了……
            【解决方案11】:

            你可以测试一下变量是否是本地定义的:

            myVar = 1
            f <- function(myvar) { 
            if( exists('myVar', environment(), inherits = FALSE) ) return( myVar) else cat("myVar was not found locally\n")
            }
            
            > f(2)
            myVar was not found locally
            

            但如果你只是想保护自己免受拼写错误,我觉得这很不自然。

            exists 函数在特定环境中搜索变量名。 inherits = FALSE 告诉它不要查看封闭的框架。

            【讨论】:

            • 要与用户交流,您应该使用messagewarningstop。这样suppressWarningssuppressMessagestryCatch就可以处理了。
            猜你喜欢
            • 2014-04-05
            • 1970-01-01
            • 2020-11-15
            • 1970-01-01
            • 2017-04-05
            • 2021-03-05
            • 1970-01-01
            • 2018-04-10
            相关资源
            最近更新 更多