Подтвердить что ты не робот

R усиливает локальный охват

Это, вероятно, не правильная терминология, но, надеюсь, я могу получить свою точку зрения.

Я часто делаю что-то вроде:

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

R с радостью использует переменную за пределами области действия, которая оставляет меня почесывать мою голову, задаваясь вопросом, как я могу получить результаты, которые я есть.

Есть ли опция, которая говорит "заставить меня использовать только переменные, которым ранее были присвоены значения в этой области действия"? Например, Perl use strict делает что-то подобное. Но я не знаю, что R имеет эквивалент my.


EDIT: Спасибо, я знаю, что я использовал их по-другому. Действительно, пример был создан специально, чтобы проиллюстрировать эту проблему!

Я хочу знать, есть ли способ, который R может автоматически предупредить меня, когда я это сделаю.

EDIT 2: Кроме того, если Rkward или другая IDE предлагают эту функциональность, я тоже хотел бы это знать.

4b9b3361

Ответ 1

Насколько я знаю, 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

Ответ 2

checkUsage в пакете codetools полезен, но не доводит вас до конца. В чистом сеансе, где myVar не определен,

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

дает

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

но как только вы определяете myVar, checkUsage счастлив.

См. ?codetools в пакете codetools: возможно, что-то там полезно:

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

Ответ 3

Использование 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

Ответ 4

Вам нужно исправить опечатку: myvar!= myvar. Тогда все будет работать...

Сфера охвата - это "изнутри", начиная с текущего, затем включается и т.д.

Изменить. Теперь, когда вы уточнили свой вопрос, посмотрите на пакетные таблицы (которые являются частью базового набора R):

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

Ответ 5

Вы, конечно, делаете это неправильно. Не ожидайте, что статические инструменты проверки кода найдут все ваши ошибки. Проверьте свой код с помощью тестов. И еще тесты. Любой достойный тест, написанный для работы в чистой среде, обнаружит такую ​​ошибку. Напишите тесты для своих функций и используйте их. Посмотрите на славу, которая представляет собой testthat пакет на CRAN.

Ответ 6

В CRAN есть новый пакет modules, который решает эту общую проблему (см. vignette здесь). С помощью 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

Это первый раз, когда я его использую. Это кажется простым, поэтому я мог бы включить его в свой обычный рабочий процесс, чтобы предотвратить многократные неудачи.

Ответ 7

вы можете динамически изменять дерево среды следующим образом:

a <- 1

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

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

f()

Внутри f, b можно найти, а a не может.

Но, вероятно, это принесет больше вреда, чем пользы.

Ответ 8

Вы можете проверить, определена ли переменная локально:

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 говорит ему не смотреть в окружающие рамки.

Ответ 9

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

удалит "рабочее пространство" из вашего пути поиска, оставьте все остальное. Это, вероятно, ближе всего к тому, что вы хотите.

Ответ 10

Что работает для меня, на основе ответа @c-urchin, заключается в определении script, который читает все мои функции, а затем исключает глобальную среду:

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 файл содержит ровно одну функцию с таким же именем, как и файл.

Ловушка заключается в том, что если одна из моих функций вызывает другую одну из моих функций, тогда внешняя функция также должна сначала вызвать этот script, и это необходимо сделать с помощью local=T:

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

предполагая, конечно, что файл script называется readfun.R.

Ответ 11

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