R强制本地范围

时间:2011-06-02 15:55:45

标签: r

这可能不是正确的术语,但希望我可以理解我的观点。

我经常最终做类似的事情:

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

R愉快地使用了函数范围之外的变量,这让我摸不着头脑,想知道我怎么可能得到我的结果。

是否有任何选项说“强迫我只使用之前已在此功能范围内指定值的变量”?例如,Perl的use strict做了类似的事情。但我不知道R有相当于my


编辑:谢谢,我知道我对他们的看法不同。实际上,这个例子专门用来说明这个问题!

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

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

11 个答案:

答案 0 :(得分:26)

据我所知,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

答案 1 :(得分:12)

checkUsage包中的

codetools会有所帮助,但不会让你一路走来。 在未定义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)

答案 2 :(得分:8)

使用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

答案 3 :(得分:7)

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

答案 4 :(得分:7)

CRAN上有一个新的包.floating-box { float: left; width: 150px; height: 75px; margin: 10px; border: 3px solid #73AD21; } 来解决这个常见问题(请参阅插图here)。使用modules时,该函数会引发错误,而不是以静默方式返回错误的结果。

modules

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

答案 5 :(得分:6)

您需要修正拼写错误: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> 

答案 6 :(得分:4)

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

a <- 1

f <- function(){
    b <- 1
    print(b)
    print(a)
}

environment(f) <- new.env(parent = baseenv())

f()

f内,可以找到b,而a则无法找到。{/ p>

但可能弊大于利。

答案 7 :(得分:3)

您可以测试以查看变量是否在本地定义:

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告诉它不要查看封闭的框架。

答案 8 :(得分:3)

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

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

答案 9 :(得分:2)

@Tommy gave a very good answer and I used it to create 3 functions that I think are more convenient in practice.

strict

to make a function strict, you just have to call

strict(f,x,y)

instead of

f(x,y)

example:

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

To get a diagnosis, execute checkStrict1(f) with optional Boolean parameters to show more ore less.

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

A more complicated case:

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

I chose to show only inexistent by default out of the 3 optional additions. You can change it easily in the function definition.

checkStrictAll

Get a diagnostic of all your potentially problematic functions, with the same parameters.

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

sources

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))
}

答案 10 :(得分:1)

根据@ 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