R6类中标准化只读主动绑定通用函数实现问题
R6类批量生成只读主动绑定字段方案
问题原因
之前的实现报错核心是两点:
- 生成的绑定函数把传入的表达式存在默认参数里,默认参数的求值环境是工厂函数的内部环境,不是R6实例的运行环境,找不到实例中的
private对象 - 没有把访问私有字段的逻辑直接注入到绑定函数的函数体中,运行时求值表达式时上下文已经丢失
方案1:无依赖Base R实现(最简便)
R6会自动把active绑定函数的封闭环境设置为实例环境,函数运行时可以直接访问self和private,不需要复杂的表达式操作,只要传私有字段名即可:
# 只读主动绑定工厂 active_readonly <- function(private_field, warn_msg = "This field is read-only") { function(value) { if (missing(value)) { # 运行时直接从实例的private环境取对应字段 private[[private_field]] } else { warning(warn_msg) } } }
类定义时直接传入对应私有字段名即可:
randomObject <- R6::R6Class("randomObject", public = list( initialize = function(size){ private$.size <- size private$.num <- sample(1:100,size) private$.letter <- sample(letters,size) private$.place <- sample(names(islands),size) }, print = function(){ cat("Random Object with ",private$.size," items:\n") cat("number(s): ",paste(private$.num, collapse = ", "),"\n") cat("letter(s): ",paste(private$.letter, collapse = ", "),"\n") cat("place(s): ",paste(private$.place, collapse = ", "),"\n") } ), private = list( .size = NULL, .num = NULL, .letter = NULL, .place = NULL ), active = list( size = active_readonly(".size", "Field size cannot be changed"), num = active_readonly(".num", "Field num cannot be changed"), letter = active_readonly(".letter", "Field letter cannot be changed"), place = active_readonly(".place", "Field place cannot be changed") ) )
测试运行完全符合预期:
test <- randomObject$new(5) test$size #> [1] 5 test$place <- "someplace" #> Warning message: #> In (function (value) : Field place cannot be changed
如果字段数量特别多,还可以写个辅助函数批量生成active列表,避免重复书写:
make_readonly_actives <- function(field_names) { setNames( lapply(field_names, function(fname) { active_readonly(paste0(".", fname), paste("Field", fname, "cannot be changed")) }), field_names ) } # 直接批量生成所有绑定 active = make_readonly_actives(c("size", "num", "letter", "place"))
方案2:rlang准引用实现(支持直接传表达式)
如果你习惯直接传访问表达式而非字符串,可以用rlang的准引用把表达式直接注入到生成的函数体中,避免环境问题:
library(rlang) active_access <- function(priv_expr, warn.msg = "Field cannot be modified"){ # 捕获传入的访问表达式 priv_expr <- enexpr(priv_expr) # 直接把表达式注入函数体,不依赖外部环境 new_function( args = pairlist(value = ), body = expr({ if(missing(value)){ !!priv_expr }else{ warning(!!warn.msg) } }) ) }
使用方式和最初的写法几乎一致:
active = list( size = active_access(private$.size, "Field size cannot be changed"), num = active_access(private$.num, "Field num cannot be changed"), letter = active_access(private$.letter, "Field letter cannot be changed"), place = active_access(private$.place, "Field place cannot be changed") )
内容的提问来源于stack exchange,提问作者Athe
相关产品推荐
相关产品推荐

