如何在不使用eval的情况下让checkargs保留qassert的变量名报错信息
问题
我希望封装checkmate库的qassert函数,实现一次性检查多个变量的规格,且断言错误时需显示不符合要求的变量名。我编写了checkargs函数循环处理输入参数,但使用get(var, envir=parent.frame())传递变量给qassert时,错误信息显示的是get(var, envir=parent.frame())这个代码字符串而非具体变量名。
qassert通过vname函数,利用deparse(eval.parent(substitute(substitute(x))))获取错误信息中的变量名。我需要找到一种方式包装变量传递逻辑,让R在deparse时得到具体变量名(如x),同时避免使用存在安全风险的eval(parse())方法。
现有示例代码:
checkargs <- function(...) { args<-list(...) for(var in names(args)) checkmate::qassert(get(var,envir=parent.frame()),args[[var]]) # 存在安全风险的字符串拼接替代方案 #eval(parse(text=paste0("qassert(",var,",'",args[[var]], "')")),parent.frame()) } test_checkargs <- function(x, y) {checkargs(x='b',y='n'); print(y)} # 正常执行场景 test_checkargs(T, 1) # [1] 1 # 错误信息不符合预期 test_checkargs(1, 1) # Error in checkargs(x = "b", y = "n") : # Assertion on 'get(var, envir = parent.frame())' failed. Must be of class 'logical', not 'double'. # # 期望错误信息: # Assertion on 'x' failed. ...
解决方案
核心思路是通过substitute构造qassert的调用表达式,保留变量的符号名,让qassert能正确捕获原始变量名。具体实现如下:
checkargs <- function(...) { args <- list(...) for (var in names(args)) { # 动态构造qassert调用表达式,将变量名转为符号 call_expr <- substitute( checkmate::qassert(var_sym, rule), list(var_sym = as.symbol(var), rule = args[[var]]) ) # 在父环境中执行构造好的表达式 eval.parent(call_expr) } }
原理说明
- 使用
as.symbol(var)将字符串形式的变量名转换为R的符号对象,这样substitute构造的表达式中,变量部分是原始的符号(如x)而非get(...)的调用。 eval.parent(call_expr)在checkargs的父环境(即调用checkargs的函数环境,比如test_checkargs)中执行表达式,确保qassert能访问到目标变量,同时内部的vname函数可以正确解析出变量名。- 这种方式避免了
eval(parse())的安全风险,因为我们直接构造抽象语法树(AST)而非解析字符串。
验证测试
运行原测试函数:
test_checkargs <- function(x, y) {checkargs(x='b',y='n'); print(y)} # 正常执行 test_checkargs(T, 1) # [1] 1 # 错误场景输出预期信息 test_checkargs(1, 1) # Error in checkargs(x = "b", y = "n") : # Assertion on 'x' failed. Must be of class 'logical', not 'double'.
内容的提问来源于stack exchange,提问作者Will
相关产品推荐
相关产品推荐

