假设我有一个功能:
f <- function() {
x + 1
}
此处x
是一个自由变量,因为它的值未在函数f
中定义。
有没有办法可以从定义的函数中获取变量名称,比如x
,比如f
?
我问这个问题,同时保持其他人的问题。旧的R代码。使用了很多自由变量,这使调试变得困难。
欢迎任何建议。
答案 0 :(得分:7)
codetools包具有用于此目的的功能,例如findGlobals
findGlobals(f, merge=FALSE)[['variables']]
# [1] "x"
如果我们将函数重新定义为具有命名参数x
,则不返回任何变量。
f2 <- function(x){
x+1
}
findGlobals(f2, merge=FALSE)[['variables']]
# character(0)
答案 1 :(得分:2)
这是一个粗略的尝试。
find_vars <- function(f, vars=list(found=character(), defined=names(formals(f)))) {
if( is.function(f) ) {
# function, begin search on body
return(find_vars(body(f), vars))
} else if (is.call(f) && deparse(f[[1]]) == "<-") {
# assignment with <- operator
if (is.recursive(f[[2]])) {
if (is.call(f[[2]]) && deparse(f[[2]][[1]]) == "$") {
vars$defined <- unique( c(vars$defined, deparse(f[[2]][[1]])) )
} else {
warning(paste("unable to determine assignments variable in", deparse(f[[2]])))
}
} else {
vars$defined <- unique( c(vars$defined, deparse(f[[2]])) )
}
vars <- find_vars(f[[3]], vars)
} else if (is.call(f) && deparse(f[[1]]) == "$") {
# assume "b" is ok in a$b
vars <- find_vars(f[[2]], vars)
} else if (is.call(f) && deparse(f[[1]]) == "~") {
#skip formulas
} else if (is.recursive(f)) {
# compound object, iterate through sub-parts
v <- lapply(as.list(f)[-1], find_vars, vars)
vars$defined <- unique( c(vars$defined, unlist(sapply(v, `[[`, "defined"))) )
vars$found <- unique( c(vars$found, unlist(sapply(v, `[[`, "found"))) )
} else if (is(f, "name")) {
# standard variable name/symbol
vars$found <- unique( c(vars$found, deparse(f)))
}
vars
}
find_free <- function(f) {
r <- find_vars(f)
return(setdiff(r$found, r$defined))
}
然后你可以像
一样使用它f <- function() {
z <- x + 1
z
}
find_free(f)
# [1] "x"
我确定有很多可能存在误报,我没有对非标准评估的功能进行任何特殊编码。例如
g <- function(df) {
with(df, mpg + disp)
}
g(head(mtcars))
# [1] 181 181 131 279 379 243
但是
find_free(g)
# [1] "mpg" "disp"
我已经为$
运算符和公式添加了一个特殊分支;您可以为具有非标准评估的函数(如with()
或subset()
或您喜欢的任何内容)添加特殊分支。这取决于你的代码最终看起来像什么。
这假设所有作业都是通过标准<-
进行的。还有其他方法可以分配不会被检测到的变量(即assign()
)。我们也忽略所有函数调用。因此,如果您致电myfun(1)
,它就不会将myfun
报告为自由变量,即使它可能是一个&#34;免费功能&#34;在代码中的其他位置定义。
所以这可能并不完美,但它应该成为解决潜在问题的合适屏幕。