R ReferenceClass子类定义中调用父类方法的缓存实现问题
解决R中ReferenceClass对象专属方法缓存的问题
针对你遇到的cache方法找不到的报错,以及对象专属缓存的需求,这里提供几种可行的解决方案:
方案1:修复原代码的装饰器引用问题
原报错是因为cache是父类的实例方法,在子类定义阶段无法直接访问。可以通过Cache$methods$cache直接获取父类的方法函数作为装饰器使用:
# 定义中缀装饰器函数 `%decorate%` = function(decorator, f) { decorator(f) } # Cache父类 Cache = setRefClass( 'Cache', fields = c( # 私有:输入到输出的哈希映射 "prev_output_hash_map__" ), methods = list( initialize = function(...) { callSuper( prev_output_hash_map__ = r2r::hashmap(), ... ) }, cache = function(f) { function(refresh = FALSE, ...) { # 哈希函数代码和输入参数 input_hash__ = rlang::hash( list( deparse1(f), ... ) ) # 检查缓存是否存在或需要刷新 if(is.null(prev_output_hash_map__[input_hash__][[1]]) || refresh) { prev_output_hash_map__[[input_hash__]] <<- f(...) } return(prev_output_hash_map__[input_hash__][[1]]) } } ) ) # 子类:使用父类方法定义作为装饰器 CachingObject = setRefClass( "CachingObject", contains = "Cache", methods = list( long_running_function = Cache$methods$cache %decorate% function(input1, input2) { Sys.sleep(10) return(input1 + input2) } ) )
说明
Cache$methods$cache直接获取父类定义的cache方法函数,解决了子类定义阶段找不到cache的问题。当实例调用方法时,ReferenceClass会自动将方法绑定到实例环境,确保prev_output_hash_map__指向当前实例的私有字段,实现对象专属缓存。
方案2:拆分缓存逻辑为通用执行函数(更清晰的结构)
放弃装饰器,改用父类提供的通用缓存执行方法,将业务逻辑与缓存逻辑分离:
library(r2r) library(rlang) # Cache父类:提供通用缓存能力 Cache = setRefClass( 'Cache', fields = list( prev_output_hash_map__ = "hashmap" ), methods = list( initialize = function(...) { prev_output_hash_map__ = hashmap() callSuper(...) }, # 通用缓存执行函数:处理缓存逻辑 cached_exec = function(func_name, ..., refresh = FALSE) { input_hash = rlang::hash(list(func_name, ...)) if (is.null(prev_output_hash_map__[[input_hash]]) || refresh) { # 调用对应的私有方法执行实际逻辑 result = do.call(paste0(func_name, "__"), list(...), envir = .self) prev_output_hash_map__[[input_hash]] = result } prev_output_hash_map__[[input_hash]] } ) ) # 子类:实现具体业务方法 CachingObject = setRefClass( "CachingObject", contains = "Cache", methods = list( # 私有方法:实际耗时逻辑 long_running_function__ = function(input1, input2) { Sys.sleep(10) input1 + input2 }, # 公开方法:调用缓存执行函数 long_running_function = function(input1, input2, refresh = FALSE) { .self$cached_exec("long_running_function", input1 = input1, input2 = input2, refresh = refresh) } ) )
测试验证
# 实例1的缓存独立 obj1 = CachingObject$new() obj1$long_running_function(1, 2) # 首次调用等待10秒 obj1$long_running_function(1, 2) # 后续调用立即返回结果 # 实例2的缓存与实例1完全隔离 obj2 = CachingObject$new() obj2$long_running_function(1, 2) # 仍需等待10秒
这个方案结构清晰,避免了装饰器的环境绑定问题,同时确保每个实例的缓存完全独立。
方案3:使用memoise包实现实例专属缓存
如果不想手动实现缓存逻辑,可以借助memoise包,为每个实例创建独立的缓存环境:
library(memoise) Cache = setRefClass( 'Cache', fields = list( # 每个实例的专属缓存环境 cache_env__ = "environment" ), methods = list( initialize = function(...) { # 创建独立缓存环境 cache_env__ = new.env(parent = emptyenv()) callSuper(...) }, # 生成实例专属的缓存装饰器 get_cacher = function() { function(f) { memoise(f, cache = cache_env__) } } ) ) CachingObject = setRefClass( "CachingObject", contains = "Cache", methods = list( initialize = function(...) { callSuper(...) # 初始化时用实例专属缓存装饰方法 .self$long_running_function = .self$get_cacher()(function(input1, input2) { Sys.sleep(10) input1 + input2 }) }, # 占位方法,会被初始化时的缓存函数覆盖 long_running_function = function(input1, input2) { stop("Method not initialized") } ) )
这个方案利用成熟的缓存库实现功能,同时通过独立环境保证每个实例的缓存不共享。
内容的提问来源于stack exchange,提问作者Canadian_Marine
相关产品推荐
相关产品推荐

